88 lines
4.3 KiB
JavaScript
88 lines
4.3 KiB
JavaScript
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))));
|
|
}
|