// Creates a demo tournament in the database so a fresh install has something to look at. // Run with the server STOPPED (the server keeps state in memory and writes it back): // DB_PATH=./data/courtside.sqlite node scripts/seed-demo.js // Options: --stage=pool (default, mid pool play) | --stage=bracket (single elim in progress) // | --stage=double (double elim in progress) import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import { Store } from '../server/store.js'; import { Registry } from '../server/state.js'; const here = dirname(fileURLToPath(import.meta.url)); const args = Object.fromEntries(process.argv.slice(2).map(a => a.replace(/^--/, '').split('='))); const STAGE = args.stage ?? 'pool'; const DB_PATH = process.env.DB_PATH ?? join(here, '..', 'data', 'courtside.sqlite'); const store = new Store(DB_PATH); const registry = new Registry(store); if (registry.get('demo-labor-day-2s')) { console.log('Demo tournament already exists at /t/demo-labor-day-2s'); process.exit(0); } const { t, engine } = registry.create({ slug: 'demo-labor-day-2s', name: 'Demo Labor Day 2s', date: '2026-09-07', courtCount: 2, rules: { teamSize: 2, pointsTo: 21, winBy: 2, cap: 25 }, notes: 'Demo data. Scores are made up.' }); const names = ['Net Gains', 'Block Party', 'Sunburnt', 'Dig It', 'Kiss My Ace', 'Setting Ducks', 'The Spikers', 'Sandbaggers', 'Serves You Right', 'Bump Chumps', 'Ace Holes', 'Beach Please']; for (const n of names) t.registerTeam({ name: n, players: 2, captain: 'Captain' }); t.closeRegistration(); t.generatePools(4); let clock = Date.now() - 90 * 60_000; engine.now = () => clock; t.now = engine.now; engine.start(); // play the first seven matches so there are standings, then leave two live with a running score let seed = 11; const rnd = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 2 ** 32); for (let i = 0; i < 7; i++) { const c = engine.courts.find(c => c.matchId); clock += (12 + rnd() * 6) * 60_000; const hi = 21, lo = Math.floor(8 + rnd() * 12); engine.recordResult(c.matchId, [rnd() < 0.5 ? [hi, lo] : [lo, hi]]); } if (STAGE !== 'pool') { // finish pool play, build the bracket, decide a few bracket matches while (engine.courts.some(c => c.matchId) && t.matches.filter(m => m.stage === 'pool' && m.status === 'pending').length + engine.courts.filter(c => c.matchId).length > 0) { const c = engine.courts.find(c => c.matchId); if (!c) break; clock += (12 + rnd() * 6) * 60_000; const lo = Math.floor(8 + rnd() * 12); engine.recordResult(c.matchId, [rnd() < 0.5 ? [21, lo] : [lo, 21]]); } const seeded = t.advanceFromPools({ perPool: 2, wildcards: 2 }); t.generateBracket(seeded, { type: STAGE === 'double' ? 'double' : 'single', thirdPlace: STAGE !== 'double' }); engine.tick(); for (let i = 0; i < (STAGE === 'double' ? 5 : 3); i++) { const c = engine.courts.find(c => c.matchId); if (!c) break; clock += (12 + rnd() * 6) * 60_000; const lo = Math.floor(8 + rnd() * 12); engine.recordResult(c.matchId, [rnd() < 0.5 ? [21, lo] : [lo, 21]]); } } const live = engine.courts.filter(c => c.matchId).map(c => t.match(c.matchId)); if (live[0]) { live[0].live = [14, 11]; live[0].startedAt = Date.now() - 9 * 60_000; } if (live[1]) { live[1].live = [7, 9]; live[1].startedAt = Date.now() - 5 * 60_000; } for (const c of engine.courts) if (c.matchId) c.startedAt = t.match(c.matchId).startedAt; t.banner = 'Demo tournament — scores are simulated'; engine.now = () => Date.now(); t.now = engine.now; registry.persist(t.slug); store.close(); console.log(`Seeded demo at /t/${t.slug}. Team codes: ${[...t.teams.values()].slice(0, 3).map(x => `${x.name}=${x.code}`).join(', ')}`);