import { readFileSync } from 'node:fs'; import { compileRules } from './src/games/mastervega/VegaRules.js'; import * as V2 from './src/games/mastervega/VegaCombatV2.js'; function mulberry32(a) { return function () { a |= 0; a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } const RULES = compileRules(JSON.parse(readFileSync('data/mastervega-rules.json','utf8'))); const techsUpTo = (tier) => { const k={}; for (const t of RULES.techList) if (t.tier<=tier) k[t.id]=true; return k; }; const mkEmp = (sid,tier)=>({known:techsUpTo(tier), traits:RULES.species[sid].traits}); const battle = (aS, aT, aShips, dS, dT, dShips, seed) => V2.runBattle(V2.createBattle(RULES, { attacker: { empireIdx: 0, name: aS, empire: mkEmp(aS, aT), ships: aShips }, defender: { empireIdx: 1, name: dS, empire: mkEmp(dS, dT), ships: dShips }, rnd: mulberry32(seed), })); const N = 400; const maxDur = RULES.combat.maxRounds * RULES.combatV2.turnSeconds; console.log('--- placement sanity check ---'); { const fleet = [{hullId:'frigate',count:5},{hullId:'destroyer',count:5},{hullId:'cruiser',count:5},{hullId:'battleship',count:2}]; const b = V2.createBattle(RULES, { attacker: { empireIdx: 0, name: 'a', empire: mkEmp('human', 6), ships: fleet }, defender: { empireIdx: 1, name: 'd', empire: mkEmp('human', 6), ships: fleet }, rnd: mulberry32(1), }); const xs = b.ships.map(s=>s.x); console.log('x range:', Math.min(...xs).toFixed(0), 'to', Math.max(...xs).toFixed(0), '(world width', RULES.combatV2.worldWidth, ')'); } console.log('--- mirror bias same-hull ---'); let worst = 0; for (const n of [1, 3, 5, 8, 15]) { let atk=0, capped=0; for (let s=1;s<=N;s+=1) { const r = battle('human', 5, [{ hullId: 'cruiser', count: n }], 'human', 5, [{ hullId: 'cruiser', count: n }], s*7919); if (r.winner==='attacker') atk+=1; if (r.rounds >= maxDur-0.01) capped += 1; } const bias = Math.abs(atk/N-0.5); worst = Math.max(worst, bias); console.log(`n=${n}: attacker ${(atk/N*100).toFixed(1)}% bias ${(bias*100).toFixed(1)}pp capped ${capped}`); } console.log('--- mixed-hull mirror (was 22.7pp) ---'); { const fleet = [{hullId:'frigate',count:5},{hullId:'destroyer',count:5},{hullId:'cruiser',count:5},{hullId:'battleship',count:2}]; let atk=0, capped=0; for (let s=1;s<=N;s+=1) { const r = battle('human',6,fleet,'human',6,fleet,s*7919); if (r.winner==='attacker') atk+=1; if (r.rounds >= maxDur-0.01) capped += 1; } const bias = Math.abs(atk/N-0.5); worst = Math.max(worst, bias); console.log(`mixed mirror: attacker ${(atk/N*100).toFixed(1)}% bias ${(bias*100).toFixed(1)}pp capped ${capped}`); } console.log('--- large fleet mirror (34 ships/side, worst-case footprint) ---'); { const fleet = [{hullId:'frigate',count:10},{hullId:'destroyer',count:10},{hullId:'cruiser',count:8},{hullId:'battleship',count:5}]; let atk=0, capped=0; const M = 150; for (let s=1;s<=M;s+=1) { const r = battle('human',6,fleet,'human',6,fleet,s*7919); if (r.winner==='attacker') atk+=1; if (r.rounds >= maxDur-0.01) capped += 1; } const bias = Math.abs(atk/M-0.5); worst = Math.max(worst, bias); console.log(`large mirror (33 ships/side): attacker ${(atk/M*100).toFixed(1)}% bias ${(bias*100).toFixed(1)}pp capped ${capped}`); } console.log('worst overall bias:', (worst*100).toFixed(1)+'pp'); console.log('--- decisive checks ---'); let atk; atk=0; for (let s=1;s<=N;s+=1) if (battle('human',6,[{hullId:'cruiser',count:5}],'human',4,[{hullId:'cruiser',count:5}],s*7919).winner==='attacker') atk+=1; console.log('two-tier tech lead:', (atk/N*100).toFixed(1)+'%'); atk=0; for (let s=1;s<=N;s+=1) if (battle('human',5,[{hullId:'cruiser',count:7}],'human',5,[{hullId:'cruiser',count:5}],s*7919).winner==='attacker') atk+=1; console.log('numbers matter (7v5):', (atk/N*100).toFixed(1)+'%'); console.log('--- determinism ---'); { const mk = (seed) => V2.createBattle(RULES, { attacker: { empireIdx: 0, name: 'a', empire: mkEmp('human', 5), ships: [{ hullId: 'cruiser', count: 4 }] }, defender: { empireIdx: 1, name: 'd', empire: mkEmp('ursaal', 5), ships: [{ hullId: 'destroyer', count: 9 }] }, rnd: mulberry32(1234), }); console.log('deterministic:', JSON.stringify(V2.runBattle(mk(1234))) === JSON.stringify(V2.runBattle(mk(1234)))); }