fix: make monerogue direct-entry only

This commit is contained in:
jwinterm
2026-07-23 14:49:34 -04:00
parent 6a3d5f8191
commit 14c8014dc4
17 changed files with 232 additions and 383 deletions
+6 -2
View File
@@ -86,13 +86,17 @@ describe('commerce disclosure and paid-action policy', () => {
});
test('publishes conspicuous no-real-value monerogue.app scope and MIT operator boundary', () => {
const disclosure = buildCommerceDisclosure(manager(), env({
const disclosure = buildCommerceDisclosure(manager({
creditsModeEnabled: false
}), env({
OPERATED_PRODUCT_PROFILE: 'such-monerogue-stagenet'
}));
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.operatedProduct.commerceSummary).toMatch(/never send mainnet XMR/i);
expect(disclosure.operatedProduct.commerceSummary).toContain('Purchased-credit entry');
expect(disclosure.service.paidCreditsEnabled).toBe(false);
expect(disclosure.service.cryptoMatchPayoutsEnabled).toBe(false);
expect(disclosure.software).toEqual(expect.objectContaining({
license: 'MIT',
+15 -7
View File
@@ -225,7 +225,7 @@ describe('EnvironmentValidator production launch gate', () => {
);
});
test('accepts the exact Such Software monerogue.app stagenet 2x/3x contract', () => {
test('accepts the exact direct-only Such Software monerogue.app stagenet 2x/3x contract', () => {
const env = productionEnv({
OPERATED_PRODUCT_PROFILE: 'such-monerogue-stagenet',
HOSTED_BY: 'https://monerogue.app',
@@ -233,16 +233,14 @@ describe('EnvironmentValidator production launch gate', () => {
OPERATOR_CONTACT_URL: 'mailto:apps@such.software',
OPERATOR_CONTACT_LABEL: 'apps@such.software',
FREE_PLAY_ENABLED: 'true',
PAYMENT_MODES: 'direct,credits',
PAYMENT_MODES: 'direct',
DIRECT_PAYMENT_ENABLED: 'true',
CREDITS_ENABLED: 'true',
CREDITS_ENABLED: 'false',
PAYOUTS_ENABLED: 'true',
DIRECT_PAYOUTS_ENABLED: 'true',
CREDITS_PAYOUTS_ENABLED: 'true',
CREDITS_PAYOUTS_ENABLED: 'false',
DIRECT_PAYOUT_ESCAPE: '2.0',
DIRECT_PAYOUT_TREASURE: '3.0',
CREDITS_PAYOUT_ESCAPE: '2.0',
CREDITS_PAYOUT_TREASURE: '3.0',
ALLOW_MAINNET_PAYOUTS: 'false',
SOLO_ENABLED: 'true',
TAVERN_ENABLED: 'true',
@@ -251,11 +249,18 @@ describe('EnvironmentValidator production launch gate', () => {
MATCH_PAYOUTS_ENABLED: 'false'
});
const operatedConfig = config({
modes: {
direct: { enabled: true, price: 10000n },
credits: {
enabled: false,
packages: [{ id: 'small', credits: 10, price: 90000000000n }]
}
},
payouts: {
enabled: true,
rules: {
direct: { enabled: true },
credits: { enabled: true }
credits: { enabled: false }
}
}
});
@@ -365,6 +370,9 @@ describe('EnvironmentValidator production launch gate', () => {
expect.stringContaining('requires OPERATOR_NAME=Such Software'),
expect.stringContaining('requires OPERATOR_CONTACT_URL=mailto:apps@such.software'),
expect.stringContaining('requires MONERO_NETWORK=stagenet'),
expect.stringContaining('requires PAYMENT_MODES=direct'),
expect.stringContaining('requires CREDITS_ENABLED=false'),
expect.stringContaining('requires CREDITS_PAYOUTS_ENABLED=false'),
expect.stringContaining('requires DIRECT_PAYOUT_ESCAPE=2'),
expect.stringContaining('requires SOLO_ENABLED=true'),
expect.stringContaining('requires TAVERN_ENABLED=true'),
+3 -3
View File
@@ -120,16 +120,16 @@ describe('GET /api/leaderboard operated-product integrity', () => {
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 () => {
test('Monerogue Champions includes direct solo only, never historical credit or match 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']);
.toEqual(['LegacyDirect']);
expect(db.calls[0].params.slice(1, 3))
.toEqual([['PAID_SINGLE', 'PAID_CREDITS'], true]);
.toEqual([['PAID_SINGLE'], true]);
});
test('independent deployments retain the generic opt-in crypto-match mapping', async () => {
+5 -2
View File
@@ -82,6 +82,7 @@ describe('production disclosure pages', () => {
currencyLabel: 'sXMR',
network: 'stagenet',
isTestNetwork: true,
creditsModeEnabled: false,
payoutsEnabled: true,
isPayoutEnabledForMode: () => true
}));
@@ -98,11 +99,13 @@ describe('production disclosure pages', () => {
currencyLabel: 'sXMR',
network: 'stagenet',
isTestNetwork: true,
creditsModeEnabled: false,
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('Only PAID_SINGLE solo runs use the Hall of Champions');
expect(terms).toContain('historical PAID_CREDITS runs and match-generated game rows are excluded');
expect(terms).toContain('does not sell credits');
expect(terms).toContain('does not offer crypto_race');
});
});
+6 -9
View File
@@ -62,7 +62,7 @@ describe('operated product environment templates', () => {
}));
});
test('monerogue.app is exactly stagenet 2x/3x solo testing with crypto-match payouts off', () => {
test('monerogue.app is exactly direct-only stagenet 2x/3x solo testing', () => {
const env = parseExample('.env.stagenet.example');
expect(env).toEqual(expect.objectContaining({
@@ -78,14 +78,14 @@ describe('operated product environment templates', () => {
CRYPTO_TYPE: 'XMR',
MONERO_NETWORK: 'stagenet',
FREE_PLAY_ENABLED: 'true',
PAYMENT_MODES: 'direct,credits',
PAYMENT_MODES: 'direct',
DIRECT_PAYMENT_ENABLED: 'true',
CREDITS_ENABLED: 'false',
PAYOUTS_ENABLED: 'true',
DIRECT_PAYOUTS_ENABLED: 'true',
CREDITS_PAYOUTS_ENABLED: 'true',
CREDITS_PAYOUTS_ENABLED: 'false',
DIRECT_PAYOUT_ESCAPE: '2.0',
DIRECT_PAYOUT_TREASURE: '3.0',
CREDITS_PAYOUT_ESCAPE: '2.0',
CREDITS_PAYOUT_TREASURE: '3.0',
ALLOW_MAINNET_PAYOUTS: 'false',
SOLO_ENABLED: 'true',
TAVERN_ENABLED: 'true',
@@ -96,9 +96,6 @@ describe('operated product environment templates', () => {
expect(BigInt(env.PAYOUT_MAX_PER_GAME)).toBeGreaterThanOrEqual(
3n * BigInt(env.DIRECT_GAME_PRICE)
);
expect(BigInt(env.PAYOUT_MAX_PER_GAME)).toBeGreaterThanOrEqual(
3n * BigInt(env.CREDITS_PAYOUT_BASE)
);
expect(env.COSMETIC_PRODUCTS).toBeUndefined();
});
@@ -109,7 +106,7 @@ describe('operated product environment templates', () => {
test.each([
['.env.mainnet.example', 'paid modes: credits'],
['.env.stagenet.example', 'paid modes: direct, credits']
['.env.stagenet.example', 'paid modes: direct']
])('%s passes the real production preflight after only secret substitution', (name, summary) => {
const result = preflightExample(name);
expect({ status: result.status, stderr: result.stderr }).toEqual({ status: 0, stderr: '' });
+1 -1
View File
@@ -43,7 +43,7 @@ describe('prestige leaderboard periods', () => {
excludeMatchGenerated: true
});
expect(soloLeaderboardPolicy('such-monerogue-stagenet', 'champions')).toEqual({
gameModes: ['PAID_SINGLE', 'PAID_CREDITS'],
gameModes: ['PAID_SINGLE'],
excludeMatchGenerated: true
});
expect(soloLeaderboardPolicy(null, 'champions')).toEqual({
+34 -27
View File
@@ -62,21 +62,21 @@ function visibleState({ player, exit, treasure }) {
describe('XMR stagenet financial canary safety contract', () => {
test('recognizes only the two explicit financial scenarios', () => {
expect(Object.keys(SCENARIOS)).toEqual(['direct-2x', 'credits-3x']);
expect(Object.keys(SCENARIOS)).toEqual(['direct-2x', 'direct-3x']);
expect(SCENARIOS['direct-2x']).toEqual(expect.objectContaining({
paymentType: 'single_game',
gameMode: 'PAID_SINGLE',
multiplier: 2,
collectTreasure: false
}));
expect(SCENARIOS['credits-3x']).toEqual(expect.objectContaining({
paymentType: 'credits_package',
gameMode: 'PAID_CREDITS',
expect(SCENARIOS['direct-3x']).toEqual(expect.objectContaining({
paymentType: 'single_game',
gameMode: 'PAID_SINGLE',
multiplier: 3,
collectTreasure: true
}));
expect(() => readConfiguration(configEnv({ E2E_SCENARIO: 'anything-else' })))
.toThrow(/direct-2x or credits-3x/);
.toThrow(/direct-2x or direct-3x/);
});
test('refuses public targets, embedded credentials, paths, and the wrong port', () => {
@@ -96,17 +96,17 @@ describe('XMR stagenet financial canary safety contract', () => {
const env = configEnv({
E2E_MODE: 'live-stagenet',
E2E_SCENARIO: 'credits-3x',
E2E_SCENARIO: 'direct-3x',
E2E_MAX_TRANSFER_ATOMIC: '500000000000',
E2E_CONFIRM: LIVE_CONFIRM,
E2E_SCENARIO_CONFIRM: SCENARIOS['credits-3x'].scenarioConfirm,
E2E_SCENARIO_CONFIRM: SCENARIOS['direct-3x'].scenarioConfirm,
E2E_CANARY_PROFILE: SAFE_PROFILE_CONFIRM,
E2E_DATABASE_NONCE_FILE: '/run/credentials/canary/database-nonce'
});
const config = readConfiguration(env);
expect(() => assertLiveSafety(config, env)).not.toThrow();
expect(() => assertLiveSafety(config, { ...env, E2E_SCENARIO_CONFIRM: 'DIRECT_2X_ESCAPE' }))
.toThrow(/CREDITS_PACKAGE_THEN_3X/);
.toThrow(/DIRECT_3X_TREASURE_ESCAPE/);
expect(() => assertLiveSafety(config, { ...env, E2E_CONFIRM: '' }))
.toThrow(/E2E_CONFIRM/);
const noNonceConfig = readConfiguration({ ...env, E2E_DATABASE_NONCE_FILE: '' });
@@ -131,7 +131,7 @@ describe('XMR stagenet financial canary safety contract', () => {
isTestNetwork: true,
paymentsEnabled: true,
directPaidEntryEnabled: true,
paidCreditsEnabled: true,
paidCreditsEnabled: false,
soloPayoutsEnabled: true,
anyPayoutsEnabled: true,
paidPrestigeOnly: false,
@@ -151,7 +151,7 @@ describe('XMR stagenet financial canary safety contract', () => {
expect(() => canonicalAcknowledgement({
...disclosure,
service: { ...disclosure.service, cryptoMatchPayoutsEnabled: true }
})).toThrow(/payout-enabled paid solo modes/);
})).toThrow(/direct-only payout-enabled solo play/);
expect(() => canonicalAcknowledgement({
...disclosure,
operatedProduct: { ...disclosure.operatedProduct, id: 'unreviewed-profile' }
@@ -166,7 +166,7 @@ describe('XMR stagenet financial canary safety contract', () => {
isTestNetwork: false,
paymentsEnabled: true,
directPaidEntryEnabled: true,
paidCreditsEnabled: true,
paidCreditsEnabled: false,
soloPayoutsEnabled: true,
anyPayoutsEnabled: true,
paidPrestigeOnly: false,
@@ -186,8 +186,8 @@ describe('XMR stagenet financial canary safety contract', () => {
payoutMultiplier: { escape: 2, escapeWithTreasure: 3 }
},
PAID_CREDITS: {
enabled: true,
payoutMultiplier: { escape: 2, escapeWithTreasure: 3 }
enabled: false,
payoutMultiplier: 0
},
match: {
enabled: true,
@@ -237,6 +237,13 @@ describe('XMR stagenet financial canary safety contract', () => {
...modes,
PAID_CREDITS: {
...modes.PAID_CREDITS,
enabled: true
}
})).toThrow(/purchased-credit solo/);
expect(() => assertPublicModeContract({
...modes,
PAID_SINGLE: {
...modes.PAID_SINGLE,
payoutMultiplier: { escape: 2, escapeWithTreasure: 4 }
}
})).toThrow(/not exactly 3x/);
@@ -252,12 +259,12 @@ describe('XMR stagenet financial canary safety contract', () => {
'monerogue_canary_direct_e2e', SCENARIOS['direct-2x']
)).not.toThrow();
expect(() => validateExpectedDatabaseName(
'monerogue_canary_credits_e2e', SCENARIOS['credits-3x']
'monerogue_canary_treasure_e2e', SCENARIOS['direct-3x']
)).not.toThrow();
expect(() => validateExpectedDatabaseName('monerogue', SCENARIOS['direct-2x']))
.toThrow(/canary and e2e/);
expect(() => validateExpectedDatabaseName(
'monerogue_canary_credits_e2e', SCENARIOS['direct-2x']
'monerogue_canary_treasure_e2e', SCENARIOS['direct-2x']
)).toThrow(/scenario tag/);
});
@@ -358,7 +365,7 @@ describe('XMR stagenet financial canary safety contract', () => {
.toThrow(/could not prove/);
});
test('direct bot avoids treasure while credits bot gets treasure before exit', () => {
test('2x direct bot avoids treasure while 3x direct bot gets treasure before exit', () => {
const directState = visibleState({
player: { x: 0, y: 0, hasTreasure: false },
treasure: [1, 0],
@@ -369,28 +376,28 @@ describe('XMR stagenet financial canary safety contract', () => {
expect(chooseMove(directKnown, directState, botState(), SCENARIOS['direct-2x']))
.toBe('down');
const creditsState = visibleState({
const treasureState = visibleState({
player: { x: 0, y: 0, hasTreasure: false },
treasure: [1, 0],
exit: [2, 0]
});
const creditsKnown = new Map();
addVisible(creditsKnown, creditsState);
const creditsBot = botState();
expect(chooseMove(creditsKnown, creditsState, creditsBot, SCENARIOS['credits-3x']))
const treasureKnown = new Map();
addVisible(treasureKnown, treasureState);
const treasureBot = botState();
expect(chooseMove(treasureKnown, treasureState, treasureBot, SCENARIOS['direct-3x']))
.toBe('right');
const collected = {
...creditsState,
...treasureState,
player: { x: 1, y: 0, hasTreasure: true },
treasure: null
};
expect(chooseMove(creditsKnown, collected, creditsBot, SCENARIOS['credits-3x']))
expect(chooseMove(treasureKnown, collected, treasureBot, SCENARIOS['direct-3x']))
.toBe('right');
});
test.each([
['direct-2x', 10n, false],
['credits-3x', 7n, true]
['direct-3x', 7n, true]
])('verifies exact game-over liability and two-party proof for %s', (scenarioName, base, treasure) => {
const scenario = SCENARIOS[scenarioName];
const serverSeed = '11'.repeat(32);
@@ -452,14 +459,14 @@ describe('XMR stagenet financial canary safety contract', () => {
expect(safe).not.toContain('password@example');
});
test('source has one non-retriable relay call, a guarded main, and both exact flows', () => {
test('source has one non-retriable relay call, a guarded main, and direct-only entry', () => {
const source = fs.readFileSync(SCRIPT, 'utf8');
expect((source.match(/funding\.rpc\('transfer'/g) || [])).toHaveLength(1);
expect(source).toMatch(/transferGate\.attempted = true;[\s\S]*do_not_relay: false/);
expect(source).toContain('if (require.main === module)');
expect(source).toContain("type: 'single_game'");
expect(source).toContain("type: 'credits_package'");
expect(source).toContain("socket.emit('auto_start'");
expect(source).not.toContain("type: 'credits_package'");
expect(source).not.toContain("socket.emit('auto_start'");
expect(source).toContain('legalAcknowledgement: acknowledgement');
expect(source).not.toContain('console.log(');
const mainBody = source.slice(source.indexOf('async function main('));