fix: enforce operated product boundaries

This commit is contained in:
jwinterm
2026-07-23 10:44:32 -04:00
parent 80d7f35e1f
commit cbaca7d45b
28 changed files with 736 additions and 96 deletions
+1
View File
@@ -154,6 +154,7 @@ A warning appears if localStorage is unavailable.
| `GET /health/live` | Process liveness probe |
| `GET /health/ready` | Dependency readiness probe (503 while degraded) |
| `GET /api/game-modes` | Current modes and pricing |
| `GET /api/leaderboard?board=pleb\|champions\|prestige&period=all\|week\|month` | One explicitly separated score board; an omitted `board` means `pleb`, while legacy `all`, blank, and unknown board values return 400 |
| `GET /api/user/:socketId/credits` | User credit balance |
| `GET /api/user/:socketId/payments` | Payment history (paginated) |
| `GET /api/user/:socketId/payouts` | Payout history (paginated) |
+6 -1
View File
@@ -194,7 +194,12 @@ Client sends:
- **Credits/prestige competitive matches** are served from authoritative `matches` and
`match_entrants` rows by `GET /api/leaderboard?board=prestige`.
- **Crypto competitive matches** create synthetic `games` rows with `game_mode = 'PAID_CREDITS'`
for the Hall of Champions board.
for the generic self-hosted Hall of Champions board. Such Software's two operated profiles keep
this economy disabled and exclude all match-generated rows from Champions.
The public leaderboard endpoint requires `board=pleb|champions|prestige`; omitting `board` safely
defaults to `pleb`. The removed mixed `board=all` behavior and unknown or blank values return 400
instead of silently combining or translating economies.
An individual win requires the durable match winner and placement #1 to agree; reaching the exit
alone is never treated as a win in last-alive or score-attack. `coop-escape` is collective, records
+3 -1
View File
@@ -127,7 +127,9 @@ Pending:
## Scoring & Leaderboard (Feb 2026)
- [x] **Scoring System**: New formula (max 800 pts) — base escape + treasure bonus + speed bonus + efficiency bonus
- [x] **Score Persistence**: `score` column on games table, `high_score` on users (migration 014)
- [x] **Leaderboard API**: `GET /api/leaderboard?period=all|week|month` with display name support
- [x] **Leaderboard API**: `GET /api/leaderboard?board=pleb|champions|prestige&period=all|week|month`
with display-name support and no mixed-economy fallback (`board` omitted → Pleb; invalid/legacy
`all` → 400)
- [x] **Leaderboard UI**: Modal with rank, name, best score, wins, games. Period tabs (All/Week/Month)
- [x] **Display Names**: `/nick` chat command stores `display_name` on users table
- [x] **Real-time Updates**: `leaderboard_update` broadcast on game wins, auto-refreshes open modal
+6 -8
View File
@@ -88,7 +88,7 @@
els.objectiveHint.textContent = 'Arrow keys / WASD · ' + activeRuleset.objective + ' · drag or pinch the camera';
els.economyHelp.textContent = activeRuleset.cooperative
? 'Co-op is a team result and does not create an individual leaderboard winner.'
: 'Free, Prestige, and Champions results remain on separate boards.';
: 'Each available entry mode keeps its results on a separate leaderboard.';
}
function renderRulesets() {
@@ -119,20 +119,18 @@
var availableKeys = Object.keys(ECONOMIES).filter(function (key) { return !!available[key]; });
if (!available[selectedEconomy] && availableKeys.length) selectedEconomy = availableKeys[0];
els.economies.innerHTML = '';
Object.keys(ECONOMIES).forEach(function (key) {
availableKeys.forEach(function (key) {
var def = ECONOMIES[key];
var locked = !available[key];
var button = document.createElement('button');
button.type = 'button';
button.className = 'economy' + (key === selectedEconomy && !locked ? ' active' : '') + (locked ? ' locked' : '');
button.className = 'economy' + (key === selectedEconomy ? ' active' : '');
button.dataset.economy = key;
button.disabled = locked;
button.setAttribute('aria-pressed', key === selectedEconomy && !locked ? 'true' : 'false');
button.setAttribute('aria-pressed', key === selectedEconomy ? 'true' : 'false');
var title = document.createElement('strong'); title.textContent = def.label;
var desc = document.createElement('small'); desc.textContent = def.description;
var avail = document.createElement('small'); avail.className = 'availability'; avail.textContent = locked ? 'NOT ENABLED' : def.available;
var avail = document.createElement('small'); avail.className = 'availability'; avail.textContent = def.available;
button.appendChild(title); button.appendChild(desc); button.appendChild(avail);
if (!locked) button.addEventListener('click', function () {
button.addEventListener('click', function () {
selectedEconomy = key;
renderEconomies();
});
+14 -5
View File
@@ -16,6 +16,7 @@ const SocketHandlers = {
_gameMode: null, // set from the server's game-modes event; was previously declared
// twice in this object literal (the later `null` silently won)
_directModeEnabled: false,
_directPayoutsEnabled: false,
_paymentsEnabled: false,
_isSmirkExplicitlyEnabled: function(data) {
@@ -897,12 +898,16 @@ const SocketHandlers = {
SocketHandlers._freePlayEnabled = !!data.freePlayEnabled;
SocketHandlers._earlyEntryConfig = data.earlyEntry || { enabled: false };
SocketHandlers._creditsPayoutsEnabled = !!data.creditsPayoutsEnabled;
SocketHandlers._directModeEnabled = !!data.directModeEnabled;
SocketHandlers._directPayoutsEnabled = !!data.directPayoutsEnabled;
SocketHandlers._paymentsEnabled = !!data.paymentsEnabled;
SocketHandlers._smirkEnabled = SocketHandlers._isSmirkExplicitlyEnabled(data);
SocketHandlers._cryptoType = data.cryptoType || 'WOW';
SocketHandlers._currencyLabel = data.currencyLabel || data.cryptoType || 'WOW'; // sXMR on stagenet
SocketHandlers._explorerTxUrl = data.explorerTxUrl || null;
if (typeof Leaderboard !== 'undefined' && Leaderboard.updateConfig) {
Leaderboard.updateConfig(data);
}
// Do not ask prestige-only users for a payout address or imply that mainnet paid
// entries award crypto. Match crypto is exposed only when its economy is admitted.
var matchCryptoPayouts = !!(data.modes && data.modes.match && data.modes.match.economies
@@ -1907,7 +1912,7 @@ const SocketHandlers = {
// Entry Choice Modal
// =====================
// ONE consolidated entry modal: timing (when) + stakes (free vs ranked) in a single dialog, so
// ONE consolidated entry modal: timing (when) + entry (free vs ranked) in a single dialog, so
// the player never hits the old timing-modal-then-payment-modal double. Each card emits the
// FINAL intent — Free sends {free:true} (server skips its options modal), Ranked uses a credit
// or opens the real payment UI. Nothing starts until a card is clicked.
@@ -1918,6 +1923,10 @@ const SocketHandlers = {
var hasCredits = !!opts.hasCredits;
var cost = this._creditsPerGame || 1;
var currency = this._currencyLabel || 'WOW';
var creditEntry = cost + (cost === 1 ? ' credit' : ' credits');
var buyRankedEntry = SocketHandlers._directModeEnabled
? 'Buy a single entry or credits with ' + currency
: 'Buy credits with ' + currency;
var timing = 'wait'; // safe default
var $overlay = $('<div id="entryChoiceOverlay">').css({
@@ -1940,7 +1949,7 @@ const SocketHandlers = {
'<span style="display:block;font-size:13px;font-weight:bold;color:' + (on ? '#fff' : '#e6ead0') + '">' + t + '</span>' +
'<span style="display:block;font-size:10.5px;margin-top:2px;">' + d + '</span></button>';
}
function stake(id, k, t, s, accent) {
function entryCard(id, k, t, s, accent) {
return '<div id="' + id + '" style="display:flex;align-items:center;gap:11px;cursor:pointer;' +
'background:#0a0f07;border:1px solid ' + accent + ';border-radius:8px;padding:11px 12px;margin-bottom:9px;">' +
'<span style="font-size:21px;width:26px;text-align:center;">' + k + '</span>' +
@@ -1975,9 +1984,9 @@ const SocketHandlers = {
seg('ecWait', timing === 'wait', '&#128737; Next block', 'Start fresh &middot; blocks land at random', false) +
seg('ecNow', timing === 'now', '&#9889; Right now', 'Jump in &middot; race the time left', true) +
'</div>' +
'<div style="font-size:10px;letter-spacing:2px;color:#5d6a4c;text-transform:uppercase;font-weight:bold;margin-bottom:7px;">2 &middot; Choose your stakes</div>' +
(freeAvailable ? stake('ecFree', '&#127379;', 'Free Play', 'No cost &middot; Pleb leaderboard', '#123a17') : '') +
stake('ecRank', '&#128176;', 'Ranked', (hasCredits ? '1 credit' : ('Pay ' + cost + ' ' + currency)) + ' &middot; Hall of Champions', '#2a1a4a') +
'<div style="font-size:10px;letter-spacing:2px;color:#5d6a4c;text-transform:uppercase;font-weight:bold;margin-bottom:7px;">2 &middot; Choose your entry</div>' +
(freeAvailable ? entryCard('ecFree', '&#127379;', 'Free Play', 'No cost &middot; Pleb leaderboard', '#123a17') : '') +
entryCard('ecRank', '&#128176;', 'Ranked', (hasCredits ? creditEntry : buyRankedEntry) + ' &middot; Hall of Champions', '#2a1a4a') +
'<div style="margin-top:6px;font-size:11px;"><a id="ecBuy" style="color:#f0a828;cursor:pointer;">&#43; Buy credits (bulk discount)</a></div>' +
'</div>'
);
+1 -1
View File
@@ -104,7 +104,7 @@ const HelpModal = {
}
const freeLine = config.freePlayEnabled
? `<div style="font-size:0.82em; color:#888; margin-top:6px;">Prefer no stakes? Free runs are available too (Pleb leaderboard).</div>`
? `<div style="font-size:0.82em; color:#888; margin-top:6px;">Prefer a free entry? Free runs are available too (Pleb leaderboard).</div>`
: '';
$container.html(`
+22 -2
View File
@@ -5,7 +5,17 @@
const Leaderboard = {
_isLoading: false,
_currentPeriod: 'all',
_currentBoard: 'champions', // solo paid | solo/free match | credits-prestige PvP
_currentBoard: 'champions', // paid solo (and generic opt-in crypto match) | free | prestige PvP
_profileId: null,
_cryptoMatchResultsEnabled: false,
updateConfig: function(config) {
config = config || {};
this._profileId = config.operatedProductProfileId || null;
this._cryptoMatchResultsEnabled = !!(config.modes && config.modes.match
&& config.modes.match.economies && config.modes.match.economies.crypto_race);
if ($('#leaderboard-panel').length) this._updateTabs();
},
init: function() {
$('#leaderboardButton').on('click', function() {
@@ -94,10 +104,20 @@ const Leaderboard = {
$(this).css({ background: '#1a1a2e', color: '#aaa', borderColor: '#555' });
}
});
var championsNote;
if (this._profileId === 'such-play-wow-prestige') {
championsNote = 'Credit-entry solo scores only; no prizes, payouts, cash-out, or crypto-match results. Never mixed with Free or Prestige.';
} else if (this._profileId === 'such-monerogue-stagenet') {
championsNote = 'Direct-entry and credit-entry solo scores only; no crypto-match results. Never mixed with Free or Prestige.';
} else if (this._cryptoMatchResultsEnabled) {
championsNote = 'Paid solo and explicitly enabled crypto-match results; never mixed with Free or Prestige.';
} else {
championsNote = 'Paid solo scores plus any historical crypto-match results recorded while that mode was enabled; never mixed with Free or Prestige.';
}
var boardMeta = {
champions: {
title: '🏅 Hall of Champions',
note: 'Paid solo and configured crypto-stakes competitive results; never mixed with Free or Prestige.'
note: championsNote
},
prestige: {
title: '✨ PvP Prestige',
+7 -3
View File
@@ -178,9 +178,13 @@ const PaymentUI = {
// Explain the two leaderboards up front when both paid and free are available.
if (this.config.paymentsEnabled && this.config.freePlayEnabled) {
const paidEntryKinds = [];
if (this.config.creditsModeEnabled) paidEntryKinds.push('credits');
if (this.config.directModeEnabled) paidEntryKinds.push('single entry');
const paidEntryCopy = paidEntryKinds.join(' or ') || 'a paid entry';
$container.append(
'<div style="margin-bottom:14px;padding:10px;background:rgba(245,158,11,0.08);border:1px solid #92400e;border-radius:6px;font-size:0.85em;line-height:1.5;">' +
'🏅 <strong style="color:#fbbf24;">Pay</strong> (credits or single entry) to put your high scores in the <strong style="color:#fbbf24;">Hall of Champions</strong>.<br>' +
'🏅 <strong style="color:#fbbf24;">Pay</strong> (' + this.escapeHtml(paidEntryCopy) + ') to put your high scores in the <strong style="color:#fbbf24;">Hall of Champions</strong>.<br>' +
'🪙 <strong style="color:#8ab4f8;">Play free</strong> — high scores go to the <strong style="color:#8ab4f8;">Pleb leaderboard</strong> instead.' +
'</div>'
);
@@ -231,13 +235,13 @@ const PaymentUI = {
$container.append(`
<div class="payment-section" style="margin-bottom:15px;padding:12px;background:#1a1a2e;border:1px solid #444;border-radius:6px;">
<div style="margin-bottom:8px;">
<strong style="color:#f0f0f0;">⚡ Single game <span style="color:#a78bfa;font-size:0.8em;">· 1 credit</span></strong>
<strong style="color:#f0f0f0;">⚡ Single-game entry</strong>
<span style="float:right;color:#fbbf24;font-weight:bold;">${price} ${currency}</span>
</div>
<div style="font-size:0.85em;color:#aaa;margin-bottom:6px;">
${payoutInfo}
</div>
<div style="font-size:0.78em;color:#888;margin-bottom:10px;">Counts as 1 credit toward cosmetic unlocks.</div>
<div style="font-size:0.78em;color:#888;margin-bottom:10px;">Adds one purchase-progress point for unlock thresholds; no spendable credit is added.</div>
<button class="mode-option" data-mode="PAID_SINGLE" data-action="pay_direct"
style="width:100%;padding:8px;background:#3730a3;border:none;color:#fff;cursor:pointer;border-radius:4px;">
💰 Pay ${price} ${currency} to Play
+2 -2
View File
@@ -44,7 +44,7 @@
button:hover:not(:disabled), button:focus-visible { border-color:#718095; filter:brightness(1.1); }
button.primary { background:linear-gradient(#67e17a,#42b957); color:#061108; border-color:#7af08b; font-weight:700; box-shadow:0 0 18px rgba(85,217,107,.14); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
.economies { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; margin:14px 0; }
.economies { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:10px; margin:14px 0; }
.economy { min-height:92px; padding:12px; text-align:left; border:1px solid var(--line); border-radius:8px; background:var(--panel2); cursor:pointer; }
.economy strong, .economy small { display:block; }
.economy strong { color:#fff; margin-bottom:5px; font-size:14px; }
@@ -126,7 +126,7 @@
<div id="panel">
<div id="queuePanel">
<h2 class="section-title">Choose your entry mode</h2>
<div id="economyHelp" class="muted">Free, Prestige, and Champions results remain on separate boards.</div>
<div id="economyHelp" class="muted">Each available entry mode keeps its results on a separate leaderboard.</div>
<div id="economies" class="economies" role="group" aria-label="Match entry mode"></div>
<div id="status" role="status" aria-live="polite">Connecting to the arena…</div>
+1 -1
View File
@@ -714,7 +714,7 @@
function buildMatchEconomies(info) {
if (!info || !info.modes || !info.modes.match || !info.modes.match.enabled) return;
var economies = info.modes.match.economies || {};
var labels = { free: 'Pleb · Free', credits_prestige: 'Prestige · Credits', crypto_race: 'Stagenet · Stakes' };
var labels = { free: 'Pleb · Free', credits_prestige: 'Prestige · Credits', crypto_race: 'Stagenet · Crypto entry' };
var available = Object.keys(labels).filter(function (key) { return !!economies[key]; });
if (!economies[selectedMatchEconomy] && available.length) selectedMatchEconomy = available[0];
matchEconomies.innerHTML = '';
+1
View File
@@ -84,6 +84,7 @@ EARLY_ENTRY_ENABLED=true
EARLY_ENTRY_FREE_MODE=true
EARLY_ENTRY_CREDITS_MODE=true
SOLO_ENABLED=true
TAVERN_ENABLED=true
TAVERN_TICK_MS=100
MATCH_ENABLED=true
+1
View File
@@ -93,6 +93,7 @@ EARLY_ENTRY_ENABLED=true
EARLY_ENTRY_FREE_MODE=true
EARLY_ENTRY_CREDITS_MODE=true
SOLO_ENABLED=true
TAVERN_ENABLED=true
TAVERN_TICK_MS=100
MATCH_ENABLED=true
+3
View File
@@ -129,6 +129,9 @@ function validateOperatedProductProfile(env = process.env, config = {}) {
requireContract(isExplicitTrue(env.PAID_ACKNOWLEDGEMENT_REQUIRED),
'PAID_ACKNOWLEDGEMENT_REQUIRED=true');
requireContract(isExplicitTrue(env.FREE_PLAY_ENABLED), 'FREE_PLAY_ENABLED=true');
requireContract(isExplicitTrue(env.SOLO_ENABLED), 'SOLO_ENABLED=true');
requireContract(isExplicitTrue(env.TAVERN_ENABLED), 'TAVERN_ENABLED=true');
requireContract(isExplicitTrue(env.MATCH_ENABLED), 'MATCH_ENABLED=true');
requireContract(isExplicitFalse(env.MATCH_CRYPTO_RACE_ENABLED), 'MATCH_CRYPTO_RACE_ENABLED=false');
requireContract(isExplicitFalse(env.MATCH_PAYOUTS_ENABLED), 'MATCH_PAYOUTS_ENABLED=false');
requireContract(!hasOutOfScopeProduct(config),
+2
View File
@@ -24,6 +24,7 @@ const { listMatchRulesets, resolveMatchRuleset } = require('./rulesets');
const { matchPayoutAdmissionPolicy } = require('../network/matchEconomyPolicy');
const { isSmirkEnabled } = require('../auth/smirkPolicy');
const { reservePayoutCapacity } = require('../services/payoutAdmissionService');
const { getOperatedProductProfile } = require('../config/operatedProductProfiles');
// A wallet "not enough (unlocked) money" error is raised BEFORE the tx is broadcast, so it
// is SAFE to retry (no double-pay risk) — unlike an ambiguous post-broadcast failure. Monero
@@ -2415,6 +2416,7 @@ class GameModeManager {
}));
return {
operatedProductProfileId: getOperatedProductProfile(process.env)?.id || null,
mode: this.gameMode,
freePlayEnabled: !!this.freePlayEnabled, // players may choose free play even on a paid instance
cryptoType: this.cryptoType,
+14 -52
View File
@@ -52,11 +52,8 @@ const { renderPrivacy, renderResponsiblePlay, renderTerms } = require('./views/l
const { buildCommerceDisclosure } = require('./config/commerceDisclosurePolicy');
const createAdminRoutes = require('./routes/admin');
const createAuthRoutes = require('./routes/auth');
const { createLeaderboardHandler } = require('./routes/leaderboard');
const { isSmirkEnabled } = require('./auth/smirkPolicy');
const {
normalizeLeaderboardPeriod,
queryPrestigeLeaderboard
} = require('./network/prestigeLeaderboard');
// Initialize payment configuration
const paymentConfigManager = new PaymentConfigManager({ logger: console });
@@ -701,54 +698,10 @@ app.use(createAdminRoutes(adminRouteCtx));
// =============================================================================
// LEADERBOARD
// =============================================================================
app.get('/api/leaderboard', asyncHandler(async (req, res) => {
const period = normalizeLeaderboardPeriod(req.query.period);
const requestedLimit = Number.parseInt(req.query.limit, 10);
const limit = Number.isFinite(requestedLimit)
? Math.max(1, Math.min(requestedLimit, 50))
: 20;
let timeFilter = '';
if (period === 'week') timeFilter = "AND g.completed_at > NOW() - INTERVAL '7 days'";
else if (period === 'month') timeFilter = "AND g.completed_at > NOW() - INTERVAL '30 days'";
// Per-game leaderboard split (whitelisted -> safe to interpolate):
// champions = games played with credits/entry-fee (Hall of Champions)
// pleb = free games (Pleb board)
// prestige = credit-prestige match races
// all = everyone (default, backward compatible)
const board = req.query.board || 'all';
let boardFilter = '';
if (board === 'champions') boardFilter = "AND g.game_mode IN ('PAID_SINGLE','PAID_CREDITS')";
else if (board === 'pleb') boardFilter = "AND g.game_mode = 'FREE'";
if (board === 'prestige') {
const result = await queryPrestigeLeaderboard(db, { period, limit });
return res.json({ leaderboard: result.rows, period, board });
}
const result = await db.query(`
SELECT
u.id,
COALESCE(u.display_name,
CASE WHEN u.payout_address IS NOT NULL
THEN LEFT(u.payout_address, 4) || '...' || RIGHT(u.payout_address, 4)
ELSE 'Anon#' || u.id
END
) as name,
MAX(g.score) as best_score,
COUNT(*) FILTER (WHERE g.status = 'won') as wins,
COUNT(*) as games_played
FROM games g
JOIN users u ON g.user_id = u.id
WHERE g.status IN ('won', 'lost') AND g.score > 0 ${timeFilter} ${boardFilter}
GROUP BY u.id, u.display_name, u.payout_address
ORDER BY best_score DESC
LIMIT $1
`, [limit]);
res.json({ leaderboard: result.rows, period, board });
}));
app.get('/api/leaderboard', asyncHandler(createLeaderboardHandler({
db,
getOperatedProductProfileId: () => currentCommerceDisclosure().operatedProduct?.id || null
})));
// Public "social proof" stats strip: players online, games + escapes in the last 24h, and
// total paid out. Cached in-memory for 10s so a public, unauthenticated endpoint can't hammer
@@ -798,6 +751,8 @@ app.get('/api/stats', asyncHandler(async (req, res) => {
app.get('/api/game-modes', (req, res) => {
const config = paymentConfigManager.getConfig();
const disclosure = currentCommerceDisclosure();
const publicModeInfo = gameModeManager.getGameModeInfo();
const decimals = Number(config.currency?.decimals ?? 12);
const toDisplay = (value) => {
if (value === null || value === undefined) {
@@ -830,6 +785,9 @@ app.get('/api/game-modes', (req, res) => {
}
res.json({
operatedProductProfileId: disclosure.operatedProduct?.id || null,
cryptoMatchPayoutsEnabled: disclosure.service.cryptoMatchPayoutsEnabled,
soloEnabled: publicModeInfo.modes?.solo === true,
// Free play is available when the instance is free-only OR when free play is offered
// as a choice alongside paid options (FREE_PLAY_ENABLED).
freePlayEnabled: !!gameModeManager.freePlayEnabled,
@@ -856,6 +814,10 @@ app.get('/api/game-modes', (req, res) => {
? gameModeManager.getImplementedPayoutMultipliersForMode('PAID_CREDITS')
: 0
},
match: {
enabled: publicModeInfo.modes?.match?.enabled === true,
economies: publicModeInfo.modes?.match?.economies || {}
},
rendererCdnEnabled,
hostedBy
});
+97 -1
View File
@@ -1,15 +1,29 @@
'use strict';
const {
OPERATED_PRODUCT_PROFILE_IDS
} = require('../config/operatedProductProfiles');
const PERIOD_MS = Object.freeze({
week: 7 * 24 * 60 * 60 * 1000,
month: 30 * 24 * 60 * 60 * 1000
});
const LEADERBOARD_BOARDS = new Set(['pleb', 'champions', 'prestige']);
function normalizeLeaderboardPeriod(value) {
const period = String(value || 'all').toLowerCase();
return Object.prototype.hasOwnProperty.call(PERIOD_MS, period) ? period : 'all';
}
function normalizeLeaderboardBoard(value) {
// A request that omits the board gets the no-entry-cost board. Explicit legacy `all`, blank,
// and unknown values are invalid: silently translating them would conceal an API contract
// change and could make a caller believe it received a mixed board.
if (value === undefined || value === null) return 'pleb';
const board = String(value).trim().toLowerCase();
return LEADERBOARD_BOARDS.has(board) ? board : null;
}
function prestigeCutoff(period, now = new Date()) {
const normalized = normalizeLeaderboardPeriod(period);
if (normalized === 'all') return null;
@@ -42,8 +56,90 @@ async function queryPrestigeLeaderboard(db, { period = 'all', limit = 20, now =
`, [cutoff, limit]);
}
function soloLeaderboardPolicy(profileId, board) {
if (board === 'pleb') {
// Free competitive matches intentionally create durable FREE rows for this board.
return Object.freeze({
gameModes: Object.freeze(['FREE']),
excludeMatchGenerated: false
});
}
if (board !== 'champions') {
throw new TypeError(`Unsupported solo leaderboard board: ${board}`);
}
if (profileId === OPERATED_PRODUCT_PROFILE_IDS.WOW_PRESTIGE) {
return Object.freeze({
// The operated Wownero product is credits-only. Historical PAID_SINGLE rows are not
// part of its current Hall of Champions.
gameModes: Object.freeze(['PAID_CREDITS']),
excludeMatchGenerated: true
});
}
if (profileId === OPERATED_PRODUCT_PROFILE_IDS.XMR_STAGENET) {
return Object.freeze({
gameModes: Object.freeze(['PAID_SINGLE', 'PAID_CREDITS']),
excludeMatchGenerated: true
});
}
// Independent MIT deployments retain the generic historical mapping, including an explicitly
// enabled crypto_race whose synthetic row is represented as PAID_CREDITS.
return Object.freeze({
gameModes: Object.freeze(['PAID_SINGLE', 'PAID_CREDITS']),
excludeMatchGenerated: false
});
}
async function querySoloLeaderboard(db, {
profileId = null,
board = 'pleb',
period = 'all',
limit = 20,
now = new Date()
} = {}) {
const policy = soloLeaderboardPolicy(profileId, board);
const cutoff = prestigeCutoff(period, now);
return db.query(`
SELECT
u.id,
COALESCE(u.display_name,
CASE WHEN u.payout_address IS NOT NULL
THEN LEFT(u.payout_address, 4) || '...' || RIGHT(u.payout_address, 4)
ELSE 'Anon#' || u.id
END
) AS name,
MAX(g.score) AS best_score,
COUNT(*) FILTER (WHERE g.status = 'won') AS wins,
COUNT(*) AS games_played
FROM games g
JOIN users u ON g.user_id = u.id
WHERE g.status IN ('won', 'lost')
AND g.score > 0
AND ($1::timestamptz IS NULL OR g.completed_at > $1::timestamptz)
AND g.game_mode = ANY($2::varchar[])
AND (
$3::boolean = FALSE
OR (
LEFT(COALESCE(g.outcome, ''), 6) <> 'match_'
AND NOT EXISTS (
SELECT 1
FROM matches source_match
WHERE source_match.id::text = g.dungeon_seed
)
)
)
GROUP BY u.id, u.display_name, u.payout_address
ORDER BY best_score DESC
LIMIT $4
`, [cutoff, policy.gameModes, policy.excludeMatchGenerated, limit]);
}
module.exports = {
normalizeLeaderboardBoard,
normalizeLeaderboardPeriod,
prestigeCutoff,
queryPrestigeLeaderboard
queryPrestigeLeaderboard,
querySoloLeaderboard,
soloLeaderboardPolicy
};
+59
View File
@@ -0,0 +1,59 @@
'use strict';
const {
normalizeLeaderboardBoard,
normalizeLeaderboardPeriod,
queryPrestigeLeaderboard,
querySoloLeaderboard
} = require('../network/prestigeLeaderboard');
const { ValidationError } = require('../utils/errors');
const ALLOWED_BOARDS = Object.freeze(['pleb', 'champions', 'prestige']);
function createLeaderboardHandler({
db,
getOperatedProductProfileId = () => null,
now = () => new Date()
} = {}) {
if (!db || typeof db.query !== 'function') {
throw new TypeError('Leaderboard handler requires a database query interface');
}
return async function leaderboardHandler(req, res) {
const period = normalizeLeaderboardPeriod(req.query.period);
const requestedLimit = Number.parseInt(req.query.limit, 10);
const limit = Number.isFinite(requestedLimit)
? Math.max(1, Math.min(requestedLimit, 50))
: 20;
const board = normalizeLeaderboardBoard(req.query.board);
if (!board) {
throw new ValidationError(
'board must be one of pleb, champions, or prestige; omit it to request pleb',
{
code: 'INVALID_LEADERBOARD_BOARD',
details: {
allowedBoards: ALLOWED_BOARDS,
missingBoardDefault: 'pleb'
}
}
);
}
const queryOptions = { period, limit, now: now() };
const result = board === 'prestige'
? await queryPrestigeLeaderboard(db, queryOptions)
: await querySoloLeaderboard(db, {
...queryOptions,
board,
profileId: getOperatedProductProfileId()
});
return res.json({ leaderboard: result.rows, period, board });
};
}
module.exports = {
ALLOWED_BOARDS,
createLeaderboardHandler
};
+48 -11
View File
@@ -27,6 +27,7 @@ const FLOOR = new Set(["'1", "'2"]);
const STEP_VECTORS = Object.freeze([[1, 0], [-1, 0], [0, 1], [0, -1]]);
const LIVE_CONFIRM = 'I_UNDERSTAND_THIS_BROADCASTS_ONE_XMR_STAGENET_TRANSFER';
const SAFE_PROFILE_CONFIRM = 'EASY_STATIC_MONSTER_ONE_LEVEL';
const OPERATED_PROFILE_ID = 'such-monerogue-stagenet';
const REQUIRED_MIGRATIONS = Object.freeze([
'035_payment_entitlements_and_entry_evidence.sql',
'037_payment_receipt_evidence.sql',
@@ -186,8 +187,14 @@ function canonicalAcknowledgement(disclosure) {
&& disclosure.service?.paidCreditsEnabled === true
&& disclosure.service?.soloPayoutsEnabled === true
&& disclosure.service?.anyPayoutsEnabled === true
&& disclosure.service?.paidPrestigeOnly === false,
&& disclosure.service?.paidPrestigeOnly === false
&& disclosure.service?.cryptoMatchPayoutsEnabled === false,
'commerce disclosure does not describe both payout-enabled paid solo modes');
assert(disclosure.operatedProduct?.id === OPERATED_PROFILE_ID,
'commerce disclosure is not the reviewed Such Software monerogue.app profile');
assert(String(disclosure.operatedProduct?.scopeNotice || '').includes('2×/3×')
&& String(disclosure.operatedProduct?.noRealValueNotice || '').includes('NO REAL VALUE'),
'commerce disclosure is missing the 2x/3x or NO REAL VALUE operated-product warning');
return Object.freeze({
policyVersion: disclosure.policyVersion,
ageEligible: true,
@@ -220,6 +227,34 @@ async function fetchCanonicalAcknowledgement(config) {
return { disclosure: result.body, acknowledgement };
}
function enabledEconomyIds(economies) {
if (!economies || typeof economies !== 'object' || Array.isArray(economies)) return [];
return Object.keys(economies)
.filter(key => economies[key] === true)
.sort();
}
function assertPublicModeContract(modes) {
assert(modes?.operatedProductProfileId === OPERATED_PROFILE_ID
&& modes?.cryptoMatchPayoutsEnabled === false,
'game-mode endpoint is not the reviewed no-crypto-PvP operated profile');
assert(modes?.soloEnabled === true && modes?.FREE?.enabled === true,
'the original solo mode and free solo entry must be enabled');
assert(modes?.PAID_SINGLE?.enabled === true
&& modes?.PAID_CREDITS?.enabled === true,
'both direct and credits paid solo modes must be enabled');
assert(modes?.match?.enabled === true
&& JSON.stringify(enabledEconomyIds(modes?.match?.economies))
=== JSON.stringify(['credits_prestige', 'free']),
'match mode must expose only the reviewed free and credits-prestige economies');
for (const mode of ['PAID_SINGLE', 'PAID_CREDITS']) {
assert(Number(modes?.[mode]?.payoutMultiplier?.escape) === 2,
`${mode} escape payout is not exactly 2x`);
assert(Number(modes?.[mode]?.payoutMultiplier?.escapeWithTreasure) === 3,
`${mode} treasure payout is not exactly 3x`);
}
}
async function publicPreflight(config) {
const ready = await jsonRequest(config.target, '/health/ready');
assert(ready.response.status === 200 && ready.body?.ready === true,
@@ -237,15 +272,7 @@ async function publicPreflight(config) {
const modes = await jsonRequest(config.target, '/api/game-modes');
assert(modes.response.status === 200, 'game-mode endpoint is not HTTP 200');
assert(modes.body?.PAID_SINGLE?.enabled === true
&& modes.body?.PAID_CREDITS?.enabled === true,
'both direct and credits paid solo modes must be enabled');
for (const mode of ['PAID_SINGLE', 'PAID_CREDITS']) {
assert(Number(modes.body?.[mode]?.payoutMultiplier?.escape) === 2,
`${mode} escape payout is not exactly 2x`);
assert(Number(modes.body?.[mode]?.payoutMultiplier?.escapeWithTreasure) === 3,
`${mode} treasure payout is not exactly 3x`);
}
assertPublicModeContract(modes.body);
const stats = await jsonRequest(config.target, '/api/stats');
assert(stats.response.status === 200
@@ -1276,7 +1303,8 @@ async function assertDatabaseSettlement(db, context) {
}
function assertModeInfo(info, config) {
assert(info?.cryptoType === 'XMR'
assert(info?.operatedProductProfileId === OPERATED_PROFILE_ID
&& info?.cryptoType === 'XMR'
&& info.network === 'stagenet'
&& info.isTestNetwork === true
&& info.currencyLabel === 'sXMR'
@@ -1287,6 +1315,12 @@ function assertModeInfo(info, config) {
&& info.directPayoutsEnabled && info.creditsPayoutsEnabled,
'mixed direct/credits mode and both payout paths must be enabled');
assert(info.smirkEnabled === false, 'Smirk must be disabled on stagenet');
assert(info.modes?.solo === true
&& info.modes?.tavern === true
&& info.modes?.match?.enabled === true
&& JSON.stringify(enabledEconomyIds(info.modes?.match?.economies))
=== JSON.stringify(['credits_prestige', 'free']),
'operated Tavern/match modes or crypto-PvP exclusion drifted');
for (const key of ['direct', 'credits']) {
assert(Number(info.payoutMultipliers?.[key]?.escape) === 2
&& Number(info.payoutMultipliers?.[key]?.escapeWithTreasure) === 3,
@@ -1549,6 +1583,7 @@ if (require.main === module) {
module.exports = {
EMPTY_FINANCIAL_KEYS,
LIVE_CONFIRM,
OPERATED_PROFILE_ID,
REQUIRED_MIGRATIONS,
SAFE_PROFILE_CONFIRM,
SCENARIOS,
@@ -1558,9 +1593,11 @@ module.exports = {
assertInvoiceNotOwned,
assertLiveSafety,
assertLocalUrl,
assertPublicModeContract,
blockedKeys,
canonicalAcknowledgement,
chooseMove,
enabledEconomyIds,
main,
objectivePoint,
readConfiguration,
+33 -2
View File
@@ -1,3 +1,7 @@
const {
OPERATED_PRODUCT_PROFILE_IDS
} = require('../config/operatedProductProfiles');
const escapeHtml = (value) => String(value == null ? '' : value).replace(/[&<>"']/g, character => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
})[character]);
@@ -38,6 +42,27 @@ function modeSummary(disclosure) {
return items.length ? `<ul>${items.join('')}</ul>` : '<p>This instance currently exposes no paid play.</p>';
}
function leaderboardMapping(disclosure) {
const profileId = disclosure.operatedProduct?.id || null;
if (profileId === OPERATED_PRODUCT_PROFILE_IDS.WOW_PRESTIGE) {
return 'FREE solo runs and free competitive matches use the Pleb board. '
+ 'Only PAID_CREDITS solo runs use the Hall of Champions; historical PAID_SINGLE '
+ 'runs and match-generated game rows are excluded. credits_prestige matches use '
+ 'the separate Prestige board. This operated profile does not offer crypto_race.';
}
if (profileId === OPERATED_PRODUCT_PROFILE_IDS.XMR_STAGENET) {
return 'FREE solo runs and free competitive matches use the Pleb board. '
+ 'PAID_SINGLE and PAID_CREDITS solo runs use the Hall of Champions; match-generated '
+ 'game rows are excluded. credits_prestige matches use the separate Prestige board. '
+ 'This operated profile does not offer crypto_race.';
}
return 'Solo scores map by entry: FREE runs use the Pleb board, while PAID_SINGLE and '
+ 'PAID_CREDITS runs use the Hall of Champions. PvP maps separately by economy: free '
+ 'matches use the Pleb board, credits_prestige matches use the Prestige board, and an '
+ 'enabled crypto_race records Hall of Champions rows. Those recorded crypto_race rows '
+ 'remain historical Hall of Champions results if that economy is later disabled.';
}
function operatorAndSoftwareStatus(disclosure) {
const operated = disclosure.operatedProduct;
const software = disclosure.software || {};
@@ -85,7 +110,7 @@ function renderTerms(disclosure) {
disclosure,
body: `
<section><h2>Who may use the service</h2><p>You may use the service only if you are at least ${escapeHtml(disclosure.minimumAge)}, can lawfully enter the transactions shown, and are acting for yourself. Do not use it where paid play, cryptocurrency transactions, prizes, or gambling-like games are prohibited.</p>${jurisdiction}</section>
<section><h2>Current service modes</h2><p>The server configuration, confirmation screen, and invoice shown for an action control that action. Solo scores map by entry: FREE runs use the Pleb board, while PAID_SINGLE and PAID_CREDITS runs use the Hall of Champions. PvP maps separately by economy: free matches use the Pleb board, credits_prestige matches use the Prestige board, and crypto_race matches use the Hall of Champions.</p>${modeSummary(disclosure)}</section>
<section><h2>Current service modes</h2><p>The server configuration, confirmation screen, and invoice shown for an action control that action. ${leaderboardMapping(disclosure)}</p>${modeSummary(disclosure)}</section>
<section><h2>Payments, credits, and outcomes</h2><p>Blockchain transfers may be irreversible. Check the network, asset, address, and exact amount before sending. A pending invoice is not an entitlement until the server records the required confirmation. Do not send funds after an invoice expires; contact the operator for manual review.</p><p>Credits, tickets, cosmetics, and leaderboard status are service entitlements tied to an anonymous browser session. They are not deposits, do not earn interest, and are not redeemable for cryptocurrency unless a specific reward-enabled game says so before entry. Clearing browser storage or losing the session token can make an account inaccessible.</p><p>Random timing, dungeon generation, player decisions, other players, network conditions, and game rules affect results. A paid entry can lose its entire entry value. Previous results do not predict future results.</p></section>
<section><h2>Fair play and acceptable use</h2><p>Do not exploit defects, automate the public service without permission, interfere with other players, evade limits, launder funds, submit unlawful content, or attempt to access another session. The operator may pause entry, quarantine ambiguous payments, reverse unconsumed service credits, or suspend access to protect players and the service. Blockchain transfers already broadcast cannot be reversed by the application.</p></section>
<section><h2>Availability and changes</h2><p>The service is experimental software and may be paused, changed, or withdrawn. Rules and prices may change only for future entries; the server records the economic terms accepted for an admitted game. Verification pages and transaction records should be retained when resolving a dispute.</p></section>
@@ -122,4 +147,10 @@ function renderResponsiblePlay(disclosure) {
});
}
module.exports = { escapeHtml, renderPrivacy, renderResponsiblePlay, renderTerms };
module.exports = {
escapeHtml,
leaderboardMapping,
renderPrivacy,
renderResponsiblePlay,
renderTerms
};
+1
View File
@@ -93,6 +93,7 @@ describe('commerce disclosure and paid-action policy', () => {
expect(disclosure.operatedProduct.scopeNotice).toContain('single-player 2×/3× outcomes are test gambling mechanics');
expect(disclosure.operatedProduct.noRealValueNotice).toContain('NO REAL VALUE');
expect(disclosure.operatedProduct.commerceSummary).toContain('never send mainnet XMR');
expect(disclosure.service.cryptoMatchPayoutsEnabled).toBe(false);
expect(disclosure.software).toEqual(expect.objectContaining({
license: 'MIT',
publisherName: 'Such Software'
+12
View File
@@ -197,6 +197,9 @@ describe('EnvironmentValidator production launch gate', () => {
DIRECT_PAYOUTS_ENABLED: 'false',
CREDITS_PAYOUTS_ENABLED: 'false',
ALLOW_MAINNET_PAYOUTS: 'false',
SOLO_ENABLED: 'true',
TAVERN_ENABLED: 'true',
MATCH_ENABLED: 'true',
MATCH_CRYPTO_RACE_ENABLED: 'false',
MATCH_PAYOUTS_ENABLED: 'false'
});
@@ -241,6 +244,9 @@ describe('EnvironmentValidator production launch gate', () => {
CREDITS_PAYOUT_ESCAPE: '2.0',
CREDITS_PAYOUT_TREASURE: '3.0',
ALLOW_MAINNET_PAYOUTS: 'false',
SOLO_ENABLED: 'true',
TAVERN_ENABLED: 'true',
MATCH_ENABLED: 'true',
MATCH_CRYPTO_RACE_ENABLED: 'false',
MATCH_PAYOUTS_ENABLED: 'false'
});
@@ -341,6 +347,9 @@ describe('EnvironmentValidator production launch gate', () => {
CREDITS_PAYOUT_ESCAPE: '2',
CREDITS_PAYOUT_TREASURE: '3',
ALLOW_MAINNET_PAYOUTS: 'true',
SOLO_ENABLED: 'false',
TAVERN_ENABLED: 'false',
MATCH_ENABLED: 'false',
MATCH_CRYPTO_RACE_ENABLED: 'true',
MATCH_PAYOUTS_ENABLED: 'true'
});
@@ -357,6 +366,9 @@ describe('EnvironmentValidator production launch gate', () => {
expect.stringContaining('requires OPERATOR_CONTACT_URL=mailto:apps@such.software'),
expect.stringContaining('requires MONERO_NETWORK=stagenet'),
expect.stringContaining('requires DIRECT_PAYOUT_ESCAPE=2'),
expect.stringContaining('requires SOLO_ENABLED=true'),
expect.stringContaining('requires TAVERN_ENABLED=true'),
expect.stringContaining('requires MATCH_ENABLED=true'),
expect.stringContaining('requires MATCH_CRYPTO_RACE_ENABLED=false'),
expect.stringContaining('requires MATCH_PAYOUTS_ENABLED=false'),
expect.stringContaining('requires ALLOW_MAINNET_PAYOUTS=false')
+14
View File
@@ -214,6 +214,20 @@ describe('GameModeManager', () => {
}
});
test('game mode info publishes only a recognized operated product profile id', () => {
const old = process.env.OPERATED_PRODUCT_PROFILE;
try {
process.env.OPERATED_PRODUCT_PROFILE = 'such-play-wow-prestige';
expect(gmm.getGameModeInfo().operatedProductProfileId)
.toBe('such-play-wow-prestige');
process.env.OPERATED_PRODUCT_PROFILE = 'unreviewed-profile';
expect(gmm.getGameModeInfo().operatedProductProfileId).toBeNull();
} finally {
if (old === undefined) delete process.env.OPERATED_PRODUCT_PROFILE;
else process.env.OPERATED_PRODUCT_PROFILE = old;
}
});
test('crypto race economy is hidden when the payout master switch is off', () => {
const keys = [
'MATCH_ENABLED', 'MATCH_CRYPTO_RACE_ENABLED', 'MATCH_PAYOUTS_ENABLED',
+180
View File
@@ -0,0 +1,180 @@
'use strict';
const createErrorMiddleware = require('../src/middleware/errorHandler');
const { createLeaderboardHandler } = require('../src/routes/leaderboard');
const SOURCE_MATCH = '11111111-1111-4111-8111-111111111111';
const COMPLETED_AT = '2026-07-20T12:00:00.000Z';
function fixtureDatabase() {
const users = new Map([
[1, { id: 1, display_name: 'CreditSolo', payout_address: null }],
[2, { id: 2, display_name: 'LegacyDirect', payout_address: null }],
[3, { id: 3, display_name: 'MatchOutcome', payout_address: null }],
[4, { id: 4, display_name: 'MatchLink', payout_address: null }],
[5, { id: 5, display_name: 'FreeSolo', payout_address: null }],
[6, { id: 6, display_name: 'FreeMatch', payout_address: null }]
]);
const games = [
{
user_id: 1, game_mode: 'PAID_CREDITS', status: 'won', outcome: 'escaped',
score: 500, dungeon_seed: 'solo-credit', completed_at: COMPLETED_AT
},
{
user_id: 2, game_mode: 'PAID_SINGLE', status: 'lost', outcome: 'caught_by_monster',
score: 450, dungeon_seed: 'legacy-direct', completed_at: COMPLETED_AT
},
{
user_id: 3, game_mode: 'PAID_CREDITS', status: 'won', outcome: 'match_winner',
score: 990, dungeon_seed: 'legacy-match-without-link', completed_at: COMPLETED_AT
},
{
user_id: 4, game_mode: 'PAID_CREDITS', status: 'won', outcome: 'escaped',
score: 980, dungeon_seed: SOURCE_MATCH, completed_at: COMPLETED_AT
},
{
user_id: 5, game_mode: 'FREE', status: 'lost', outcome: 'caught_by_monster',
score: 300, dungeon_seed: 'solo-free', completed_at: COMPLETED_AT
},
{
user_id: 6, game_mode: 'FREE', status: 'won', outcome: 'match_winner',
score: 350, dungeon_seed: SOURCE_MATCH, completed_at: COMPLETED_AT
}
];
const calls = [];
return {
calls,
async query(sql, params) {
calls.push({ sql, params });
const [cutoff, gameModes, excludeMatchGenerated, limit] = params;
const rows = games.filter(game => {
if (!gameModes.includes(game.game_mode)) return false;
if (!['won', 'lost'].includes(game.status) || game.score <= 0) return false;
if (cutoff && new Date(game.completed_at) <= cutoff) return false;
if (excludeMatchGenerated
&& (String(game.outcome || '').startsWith('match_')
|| game.dungeon_seed === SOURCE_MATCH)) return false;
return true;
});
const aggregate = new Map();
for (const game of rows) {
const user = users.get(game.user_id);
const current = aggregate.get(user.id) || {
id: user.id,
name: user.display_name || `Anon#${user.id}`,
best_score: 0,
wins: 0,
games_played: 0
};
current.best_score = Math.max(current.best_score, game.score);
current.wins += game.status === 'won' ? 1 : 0;
current.games_played += 1;
aggregate.set(user.id, current);
}
return {
rows: Array.from(aggregate.values())
.sort((a, b) => b.best_score - a.best_score)
.slice(0, limit)
};
}
};
}
async function invokeLeaderboardRoute(profileId, query = {}) {
const db = fixtureDatabase();
const handler = createLeaderboardHandler({
db,
getOperatedProductProfileId: () => profileId,
now: () => new Date('2026-07-21T12:00:00.000Z')
});
const req = { query, path: '/api/leaderboard', method: 'GET' };
const response = { status: 200, body: null };
const res = {
status(status) {
response.status = status;
return this;
},
json(body) {
response.body = body;
return this;
}
};
try {
await handler(req, res);
} catch (error) {
createErrorMiddleware({ logger: { error: jest.fn() } })(error, req, res, () => {});
}
return { response, db };
}
describe('GET /api/leaderboard operated-product integrity', () => {
test('Wownero Champions includes credit-entry solo only', async () => {
const { response, db } = await invokeLeaderboardRoute('such-play-wow-prestige', {
board: 'champions',
period: 'all'
});
expect(response.status).toBe(200);
expect(response.body.board).toBe('champions');
expect(response.body.leaderboard.map(row => row.name)).toEqual(['CreditSolo']);
expect(db.calls[0].params.slice(1, 3)).toEqual([['PAID_CREDITS'], true]);
});
test('Monerogue Champions includes direct and credit solo, never match-generated rows', async () => {
const { response, db } = await invokeLeaderboardRoute('such-monerogue-stagenet', {
board: 'champions',
period: 'all'
});
expect(response.status).toBe(200);
expect(response.body.leaderboard.map(row => row.name))
.toEqual(['CreditSolo', 'LegacyDirect']);
expect(db.calls[0].params.slice(1, 3))
.toEqual([['PAID_SINGLE', 'PAID_CREDITS'], true]);
});
test('independent deployments retain the generic opt-in crypto-match mapping', async () => {
const { response, db } = await invokeLeaderboardRoute(null, {
board: 'champions',
period: 'all'
});
expect(response.status).toBe(200);
expect(response.body.leaderboard.map(row => row.name)).toEqual([
'MatchOutcome',
'MatchLink',
'CreditSolo',
'LegacyDirect'
]);
expect(db.calls[0].params.slice(1, 3))
.toEqual([['PAID_SINGLE', 'PAID_CREDITS'], false]);
});
test('missing board defaults to Pleb and preserves free competitive rows', async () => {
const { response, db } = await invokeLeaderboardRoute('such-play-wow-prestige', {
period: 'all'
});
expect(response.status).toBe(200);
expect(response.body.board).toBe('pleb');
expect(response.body.leaderboard.map(row => row.name))
.toEqual(['FreeMatch', 'FreeSolo']);
expect(db.calls[0].params.slice(1, 3)).toEqual([['FREE'], false]);
});
test.each(['all', 'unknown', ''])(
'explicit deprecated or invalid board %p returns 400 without querying',
async board => {
const { response, db } = await invokeLeaderboardRoute(
'such-play-wow-prestige',
{ board }
);
expect(response.status).toBe(400);
expect(response.body).toEqual(expect.objectContaining({
error: 'INVALID_LEADERBOARD_BOARD',
details: expect.objectContaining({
allowedBoards: ['pleb', 'champions', 'prestige'],
missingBoardDefault: 'pleb'
})
}));
expect(db.calls).toHaveLength(0);
}
);
});
+19 -1
View File
@@ -38,7 +38,8 @@ describe('production disclosure pages', () => {
expect(html).toContain('FREE runs use the Pleb board');
expect(html).toContain('PAID_SINGLE and PAID_CREDITS runs use the Hall of Champions');
expect(html).toContain('credits_prestige matches use the Prestige board');
expect(html).toContain('crypto_race matches use the Hall of Champions');
expect(html).toContain('enabled crypto_race records Hall of Champions rows');
expect(html).toContain('remain historical Hall of Champions results');
});
test('generic self-hosted pages carry the MIT conditions and third-party operator disclaimer', () => {
@@ -67,6 +68,9 @@ describe('production disclosure pages', () => {
expect(html).toContain('offers no prizes, winnings, cash-out, or payouts');
expect(html).toContain('not offered or marketed as gambling');
expect(html).toContain('legal classification depends on applicable law');
expect(html).toContain('Only PAID_CREDITS solo runs use the Hall of Champions');
expect(html).toContain('historical PAID_SINGLE runs and match-generated game rows are excluded');
expect(html).toContain('does not offer crypto_race');
expect(html).not.toContain('NO REAL VALUE');
});
@@ -86,5 +90,19 @@ describe('production disclosure pages', () => {
expect(html).toContain('NO REAL VALUE');
expect(html).toContain('Never send mainnet XMR');
expect(html).toContain('no real-money or mainnet gambling is offered');
const terms = renderTerms(disclosure({
OPERATED_PRODUCT_PROFILE: 'such-monerogue-stagenet'
}, {
cryptoType: 'XMR',
currencyLabel: 'sXMR',
network: 'stagenet',
isTestNetwork: true,
payoutsEnabled: true,
isPayoutEnabledForMode: () => true
}));
expect(terms).toContain('PAID_SINGLE and PAID_CREDITS solo runs use the Hall of Champions');
expect(terms).toContain('match-generated game rows are excluded');
expect(terms).toContain('does not offer crypto_race');
});
});
+6
View File
@@ -54,6 +54,9 @@ describe('operated product environment templates', () => {
DIRECT_PAYOUTS_ENABLED: 'false',
CREDITS_PAYOUTS_ENABLED: 'false',
ALLOW_MAINNET_PAYOUTS: 'false',
SOLO_ENABLED: 'true',
TAVERN_ENABLED: 'true',
MATCH_ENABLED: 'true',
MATCH_CRYPTO_RACE_ENABLED: 'false',
MATCH_PAYOUTS_ENABLED: 'false'
}));
@@ -84,6 +87,9 @@ describe('operated product environment templates', () => {
CREDITS_PAYOUT_ESCAPE: '2.0',
CREDITS_PAYOUT_TREASURE: '3.0',
ALLOW_MAINNET_PAYOUTS: 'false',
SOLO_ENABLED: 'true',
TAVERN_ENABLED: 'true',
MATCH_ENABLED: 'true',
MATCH_CRYPTO_RACE_ENABLED: 'false',
MATCH_PAYOUTS_ENABLED: 'false'
}));
+56 -1
View File
@@ -1,7 +1,10 @@
const {
normalizeLeaderboardBoard,
normalizeLeaderboardPeriod,
prestigeCutoff,
queryPrestigeLeaderboard
queryPrestigeLeaderboard,
querySoloLeaderboard,
soloLeaderboardPolicy
} = require('../src/network/prestigeLeaderboard');
describe('prestige leaderboard periods', () => {
@@ -21,6 +24,58 @@ describe('prestige leaderboard periods', () => {
expect(normalizeLeaderboardPeriod('YEAR')).toBe('all');
});
test.each([
['pleb', 'pleb'],
['CHAMPIONS', 'champions'],
[' prestige ', 'prestige'],
[undefined, 'pleb'],
[null, 'pleb'],
['', null],
['all', null],
['unexpected', null]
])('leaderboard board %p has an explicit, non-mixing API interpretation', (input, expected) => {
expect(normalizeLeaderboardBoard(input)).toBe(expected);
});
test('operated profile policies classify Champions from server identity', () => {
expect(soloLeaderboardPolicy('such-play-wow-prestige', 'champions')).toEqual({
gameModes: ['PAID_CREDITS'],
excludeMatchGenerated: true
});
expect(soloLeaderboardPolicy('such-monerogue-stagenet', 'champions')).toEqual({
gameModes: ['PAID_SINGLE', 'PAID_CREDITS'],
excludeMatchGenerated: true
});
expect(soloLeaderboardPolicy(null, 'champions')).toEqual({
gameModes: ['PAID_SINGLE', 'PAID_CREDITS'],
excludeMatchGenerated: false
});
expect(soloLeaderboardPolicy('such-play-wow-prestige', 'pleb')).toEqual({
gameModes: ['FREE'],
excludeMatchGenerated: false
});
});
test('operated solo query excludes both durable match markers', async () => {
const db = { query: jest.fn().mockResolvedValue({ rows: [] }) };
await querySoloLeaderboard(db, {
profileId: 'such-play-wow-prestige',
board: 'champions',
period: 'week',
limit: 17,
now
});
const [sql, params] = db.query.mock.calls[0];
expect(sql).toContain("LEFT(COALESCE(g.outcome, ''), 6) <> 'match_'");
expect(sql).toContain('source_match.id::text = g.dungeon_seed');
expect(sql).toContain('g.game_mode = ANY($2::varchar[])');
expect(params[0].toISOString()).toBe('2026-07-14T16:00:00.000Z');
expect(params[1]).toEqual(['PAID_CREDITS']);
expect(params[2]).toBe(true);
expect(params[3]).toBe(17);
});
test.each(['week', 'month', 'all'])('queries match rows with a strict boundary for %s', async (period) => {
const db = { query: jest.fn().mockResolvedValue({ rows: [] }) };
await queryPrestigeLeaderboard(db, { period, limit: 17, now });
+31
View File
@@ -75,7 +75,10 @@ describe('public multiplayer controls and boards', () => {
const matchHtml = fs.readFileSync(path.join(__dirname, '../html/match.html'), 'utf8');
const tavernHtml = fs.readFileSync(path.join(__dirname, '../html/tavern.html'), 'utf8');
const matchClient = fs.readFileSync(path.join(__dirname, '../html/js/matchClient.js'), 'utf8');
const socketHandlers = fs.readFileSync(path.join(__dirname, '../html/js/network/socketHandlers.js'), 'utf8');
const helpModal = fs.readFileSync(path.join(__dirname, '../html/js/ui/helpModal.js'), 'utf8');
const leaderboard = fs.readFileSync(path.join(__dirname, '../html/js/ui/leaderboard.js'), 'utf8');
const paymentUi = fs.readFileSync(path.join(__dirname, '../html/js/ui/paymentUI.js'), 'utf8');
const designDoc = fs.readFileSync(path.join(__dirname, '../docs/TAVERN_AND_MULTIPLAYER.md'), 'utf8');
test('match and Tavern D-pads are named groups activated through native click', () => {
@@ -92,6 +95,34 @@ describe('public multiplayer controls and boards', () => {
expect(leaderboard).toContain('never mixed with Free or the Hall of Champions');
});
test('arena renders only server-advertised economies, never locked out-of-profile cards', () => {
expect(matchClient).toContain('availableKeys.forEach(function (key)');
expect(matchClient).not.toContain('Object.keys(ECONOMIES).forEach(function (key)');
expect(matchClient).not.toContain("avail.textContent = locked ? 'NOT ENABLED'");
expect(tavernHtml).toContain('if (!economies[key]) return;');
});
test('Wownero-facing solo and leaderboard copy uses entry/credits, not stake claims', () => {
expect(socketHandlers).toContain('Choose your entry');
expect(socketHandlers).not.toContain('Choose your stakes');
expect(socketHandlers).toContain("Buy credits with ' + currency");
expect(helpModal).toContain('Prefer a free entry?');
expect(helpModal).not.toContain('Prefer no stakes?');
expect(leaderboard).toContain("this._profileId === 'such-play-wow-prestige'");
expect(leaderboard).toContain('Credit-entry solo scores only');
expect(leaderboard).toContain('historical crypto-match results recorded while that mode was enabled');
expect(leaderboard).not.toContain('crypto-stakes');
});
test('direct-entry payment copy never claims it adds a spendable credit', () => {
expect(socketHandlers).toContain('Buy a single entry or credits with ');
expect(socketHandlers).toContain('SocketHandlers._directModeEnabled');
expect(paymentUi).toContain('Single-game entry');
expect(paymentUi).toContain('no spendable credit is added');
expect(paymentUi).not.toContain('Single game <span');
expect(paymentUi).not.toContain('Counts as 1 credit toward cosmetic unlocks');
});
test('operator docs require restart for mode/ruleset changes and do not promise future combat', () => {
expect(designDoc).toContain('restart the service');
expect(designDoc).toContain('does not hot-reload match modes or rulesets');
+96 -4
View File
@@ -6,6 +6,7 @@ const path = require('path');
const {
LIVE_CONFIRM,
OPERATED_PROFILE_ID,
REQUIRED_MIGRATIONS,
SAFE_PROFILE_CONFIRM,
SCENARIOS,
@@ -13,8 +14,10 @@ const {
assertEmptyFinancialDatabase,
assertInvoiceNotOwned,
assertLiveSafety,
assertPublicModeContract,
canonicalAcknowledgement,
chooseMove,
enabledEconomyIds,
readConfiguration,
safeMessage,
validateExpectedDatabaseName,
@@ -114,9 +117,14 @@ describe('XMR stagenet financial canary safety contract', () => {
});
test('builds exactly the current five-field stagenet acknowledgement', () => {
const acknowledgement = canonicalAcknowledgement({
const disclosure = {
policyVersion: '2026-07-21-v2',
paidAcknowledgementRequired: true,
operatedProduct: {
id: OPERATED_PROFILE_ID,
scopeNotice: 'Single-player 2×/3× outcomes use test coins.',
noRealValueNotice: 'NO REAL VALUE'
},
service: {
cryptoType: 'XMR',
network: 'stagenet',
@@ -126,9 +134,11 @@ describe('XMR stagenet financial canary safety contract', () => {
paidCreditsEnabled: true,
soloPayoutsEnabled: true,
anyPayoutsEnabled: true,
paidPrestigeOnly: false
paidPrestigeOnly: false,
cryptoMatchPayoutsEnabled: false
}
});
};
const acknowledgement = canonicalAcknowledgement(disclosure);
expect(acknowledgement).toEqual({
policyVersion: '2026-07-21-v2',
ageEligible: true,
@@ -138,9 +148,18 @@ describe('XMR stagenet financial canary safety contract', () => {
});
expect(Object.keys(acknowledgement)).toHaveLength(5);
expect(Object.isFrozen(acknowledgement)).toBe(true);
expect(() => canonicalAcknowledgement({
...disclosure,
service: { ...disclosure.service, cryptoMatchPayoutsEnabled: true }
})).toThrow(/payout-enabled paid solo modes/);
expect(() => canonicalAcknowledgement({
...disclosure,
operatedProduct: { ...disclosure.operatedProduct, id: 'unreviewed-profile' }
})).toThrow(/reviewed Such Software/);
expect(() => canonicalAcknowledgement({
policyVersion: 'v1',
paidAcknowledgementRequired: true,
operatedProduct: disclosure.operatedProduct,
service: {
cryptoType: 'XMR',
network: 'mainnet',
@@ -150,11 +169,84 @@ describe('XMR stagenet financial canary safety contract', () => {
paidCreditsEnabled: true,
soloPayoutsEnabled: true,
anyPayoutsEnabled: true,
paidPrestigeOnly: false
paidPrestigeOnly: false,
cryptoMatchPayoutsEnabled: false
}
})).toThrow(/stagenet/);
});
test('requires the exact public profile and 2x/3x solo outcomes with crypto PvP off', () => {
const modes = {
operatedProductProfileId: OPERATED_PROFILE_ID,
cryptoMatchPayoutsEnabled: false,
soloEnabled: true,
FREE: { enabled: true },
PAID_SINGLE: {
enabled: true,
payoutMultiplier: { escape: 2, escapeWithTreasure: 3 }
},
PAID_CREDITS: {
enabled: true,
payoutMultiplier: { escape: 2, escapeWithTreasure: 3 }
},
match: {
enabled: true,
economies: {
free: true,
credits_prestige: true
}
}
};
expect(() => assertPublicModeContract(modes)).not.toThrow();
expect(() => assertPublicModeContract({
...modes, operatedProductProfileId: 'unreviewed-profile'
})).toThrow(/reviewed no-crypto-PvP/);
expect(() => assertPublicModeContract({
...modes, cryptoMatchPayoutsEnabled: true
})).toThrow(/reviewed no-crypto-PvP/);
expect(() => assertPublicModeContract({
...modes,
soloEnabled: false
})).toThrow(/original solo mode/);
expect(() => assertPublicModeContract({
...modes,
match: { ...modes.match, economies: { free: true } }
})).toThrow(/free and credits-prestige/);
expect(() => assertPublicModeContract({
...modes,
match: {
...modes.match,
economies: {
...modes.match.economies,
crypto_race: true
}
}
})).toThrow(/free and credits-prestige/);
expect(() => assertPublicModeContract({
...modes,
match: {
...modes.match,
economies: {
...modes.match.economies,
future_paid_mode: true
}
}
})).toThrow(/free and credits-prestige/);
expect(() => assertPublicModeContract({
...modes,
PAID_CREDITS: {
...modes.PAID_CREDITS,
payoutMultiplier: { escape: 2, escapeWithTreasure: 4 }
}
})).toThrow(/not exactly 3x/);
expect(enabledEconomyIds({
free: true,
credits_prestige: true,
crypto_race: false
})).toEqual(['credits_prestige', 'free']);
});
test('requires a scenario-named dedicated canary E2E database', () => {
expect(() => validateExpectedDatabaseName(
'monerogue_canary_direct_e2e', SCENARIOS['direct-2x']