Files
wownerogue/test/matchLeaderboard.test.js
T
jwinterm 0f68eec26b feat: real-time multiplayer race mode (free, prestige, crypto)
Adds a complete block-cadence multiplayer race mode alongside solo and tavern.
  Disabled by default; enable with MATCH_ENABLED=true.

  Highlights:
  - New shared engine: MatchRoom, MatchEngine, MatchState (transport-agnostic, unit-tested).
  - Per-economy queues: free, credits_prestige, crypto_race with atomic escrow/refund.
  - MatchScheduler drains queues on every new block and starts 2–32 player races.
  - MatchManager handles Socket.IO lifecycle, server-authoritative movement, reconnect grace,
    forfeit/AFK handling, and end-of-match persistence.
  - Crypto races use race-entry tickets (grants.race_entries) to avoid on-chain refunds; winner
    payout is queued through the existing batch payout/retry service.
  - Leaderboard integration: free → Pleb, prestige → /api/leaderboard?board=prestige,
    crypto → Hall of Champions.
  - Tavern integration: Queue for Race panel, active race list, and live spectator rendering.
  - Dedicated /match.html client with economy selector, race HUD, and post-match scoreboard.
  - Migration 022 adds matches, match_entrants, match_events, match_queue_entries,
    race_entry_transactions, users.race_entries, and payouts.match_id.

  Docs:
  - New docs/MATCH_MODE.md with full architecture, config, events, and operational notes.
  - Updated README.md, docs/TAVERN_AND_MULTIPLAYER.md, docs/TODO.md, src/.env.example,
    and src/.env.

  Tests: 40 new unit tests across 7 suites, all passing.

  Known follow-ups for real-world validation:
  - Full integration test with Postgres + Socket.IO.
  - Server boot check with MATCH_ENABLED=false (default) to ensure solo/tavern remain unaffected.
  - Browser render-kit verification in /match.html and tavern spectator.
  - Crypto-race payout end-to-end with real wallet-rpc.
2026-07-09 13:26:52 -04:00

68 lines
2.5 KiB
JavaScript

const MatchLeaderboard = require('../src/network/matchLeaderboard');
const MatchRoom = require('../src/multiplayer/MatchRoom');
describe('MatchLeaderboard', () => {
function makeDb() {
const captured = [];
return {
captured,
async withTransaction(fn) {
const client = {
async query(sql, params) {
captured.push({ sql, params });
return { rowCount: 1, rows: [] };
}
};
return await fn(client);
}
};
}
function makeFinishedRoom(economy) {
const room = new MatchRoom({
entrants: {
a: { userId: 1, name: 'A' },
b: { userId: 2, name: 'B' }
},
economy,
seed: '0000000000000000000000000000000000000000000000000000000000000000'
});
room.start();
room.expire('hard_ceiling');
room.finalize();
return room;
}
test('free race inserts synthetic FREE records', async () => {
const db = makeDb();
const lb = new MatchLeaderboard({ db });
const room = makeFinishedRoom('free');
await lb.postMatch(room);
const gamesInserts = db.captured.filter(q => q.sql.includes('INSERT INTO games')) || [];
expect(gamesInserts.length).toBe(2);
expect(gamesInserts[0].params[2]).toBe('FREE');
});
test('prestige race broadcasts without inserting games', async () => {
const emitted = [];
const io = { emit: (e, p) => emitted.push({ e, p }) };
const db = makeDb();
const lb = new MatchLeaderboard({ db, io });
const room = makeFinishedRoom('credits_prestige');
await lb.postMatch(room);
const gamesInserts = db.captured.filter(q => q.sql.includes('INSERT INTO games')) || [];
expect(gamesInserts.length).toBe(0);
expect(emitted.some(e => e.e === 'leaderboard_update' && e.p.board === 'prestige')).toBe(true);
});
test('crypto race inserts PAID_CREDITS records', async () => {
const db = makeDb();
const lb = new MatchLeaderboard({ db });
const room = makeFinishedRoom('crypto_race');
await lb.postMatch(room);
const gamesInserts = db.captured.filter(q => q.sql.includes('INSERT INTO games')) || [];
expect(gamesInserts.length).toBe(2);
expect(gamesInserts[0].params[2]).toBe('PAID_CREDITS');
});
});