|
| 1 | +'use strict'; |
| 2 | +// V7 preset reality smoke: real canonical-AI battles across the 60 presets. |
| 3 | +// * 12-rarity representative matrix (one card per rarity tier, mirrored pairs, |
| 4 | +// multiple fixed Match Seeds) -> the Level x Rarity hierarchy must hold. |
| 5 | +// * 3v3 smoke (explicit 1-6 team selection, canonical AI, new Match Seed). |
| 6 | +// * Side bias: team A vs team B must stay neutral. |
| 7 | +// This is release evidence, NOT part of the CI regression. |
| 8 | +const fs=require('node:fs'); |
| 9 | +const path=require('node:path'); |
| 10 | +const ROOT=path.resolve(__dirname,'..'); |
| 11 | +const {emptyBattleCounts,addBattleCounts,scoreMirroredPair,summarizeBattleCounts,wilson95}=require('../src/strength-audit-v6.js'); |
| 12 | +for(const file of ['kernel','components','rules','content','status-runtime','formula','validator','effects','engine','ai','power','power-v5','battlepower-v3','gen-stats','gen-skills','generator','gen-names','name-generator-v2','name-generator-v3','gen-v2','gen-v3','gen-v4','gen-v5','budget-v6','budget-price','gen-v6','strength-geometry-v7','style-genome-v7','strength-model-v7','solver-v7','gen-v7','battlepower-v4','presets','presets-v6','presets-v7'])require(path.join(ROOT,'src',file+'.js')); |
| 13 | +const N=global.NCB; |
| 14 | +const cards=N.SYSTEM_PRESETS_V7; |
| 15 | +if(cards.length!==60)throw new Error('expected 60 v7 presets'); |
| 16 | +const seedsPerPair=Number(process.argv[2]||2),roundCap=100; |
| 17 | +function fight(a,b,seed){ |
| 18 | + N.deployCard(a);N.deployCard(b); |
| 19 | + const engine=N.createBattle({seed:N.deriveSeed(seed),teamA:[a.id],teamB:[b.id],maxRounds:roundCap}); |
| 20 | + let guard=0;while(!engine.outcome().ended&&guard++<roundCap*2+40)engine.resolveRound([...N.planAI(engine,'A','canonical'),...N.planAI(engine,'B','canonical')]); |
| 21 | + const winner=engine.outcome().winner;return winner==='A'?1:winner==='B'?-1:0; |
| 22 | +} |
| 23 | +// --- 12-rarity representative matrix --- |
| 24 | +const byRarity=new Map();for(const card of cards){if(!byRarity.has(card.rarity))byRarity.set(card.rarity,card);} |
| 25 | +const reps=N.RARITY_V2_ORDER.map(rarity=>byRarity.get(rarity)); |
| 26 | +const matrix=[]; |
| 27 | +for(let i=0;i<reps.length;i++)for(let j=i+1;j<reps.length;j++){ |
| 28 | + const low=reps[i],high=reps[j],counts=emptyBattleCounts(); |
| 29 | + for(let k=0;k<seedsPerPair;k++)addBattleCounts(counts,scoreMirroredPair(low,high,700000+i*1000+j*17+k,fight)); |
| 30 | + const summary=summarizeBattleCounts(counts); |
| 31 | + matrix.push({a:{id:low.id,rarity:low.rarity,level:low.level},b:{id:high.id,rarity:high.rarity,level:high.level},...summary}); |
| 32 | +} |
| 33 | +// higher tier should win the majority of strictly-higher tier pairs (theta monotone) |
| 34 | +const decidedMatrix=matrix.filter(row=>row.higherWins!==row.lowerWins); |
| 35 | +const hierarchyUpsets=decidedMatrix.filter(row=>{const aTheta=N.targetThetaV7(row.a.level,row.a.rarity),bTheta=N.targetThetaV7(row.b.level,row.b.rarity);return Math.sign(aTheta-bTheta)!==Math.sign(row.higherWins-row.lowerWins);}); |
| 36 | +// --- 3v3 smoke: two explicit 3-card teams, canonical AI, new Match Seed --- |
| 37 | +const teamCards=cards.slice(0,6); // 6 distinct preset cards |
| 38 | +function fight3v3(seed){ |
| 39 | + const teamA=teamCards.slice(0,3),teamB=teamCards.slice(3,6); |
| 40 | + for(const card of teamA.concat(teamB))N.deployCard(card); |
| 41 | + const engine=N.createBattle({seed:N.deriveSeed(seed),teamA:teamA.map(c=>c.id),teamB:teamB.map(c=>c.id),maxRounds:roundCap}); |
| 42 | + let guard=0;while(!engine.outcome().ended&&guard++<roundCap*2+40)engine.resolveRound([...N.planAI(engine,'A','canonical'),...N.planAI(engine,'B','canonical')]); |
| 43 | + const winner=engine.outcome().winner; |
| 44 | + return {winner,rounds:engine.roundIndex,ended:engine.outcome().ended}; |
| 45 | +} |
| 46 | +const threeVsThree=[];let teamAWins=0,teamBWins=0,draws=0; |
| 47 | +for(let k=0;k<Math.max(4,seedsPerPair*2);k++){ |
| 48 | + const r=fight3v3(830000+k); |
| 49 | + if(r.winner==='A')teamAWins++;else if(r.winner==='B')teamBWins++;else draws++; |
| 50 | + threeVsThree.push(r); |
| 51 | +} |
| 52 | +const sideBattles=teamAWins+teamBWins,ci=wilson95(teamAWins,sideBattles); |
| 53 | +const artifact={schemaVersion:1,generatorVersion:7,methodology:{presets:cards.length,representativeMatrix:{pairs:matrix.length,matchSeedsPerPair:seedsPerPair,mirroredSides:true,canonicalAI:true,roundCap},threeVsThree:{teams:'explicit slots, no autofill',cards:teamCards.map(c=>c.id),matchSeeds:threeVsThree.length}}, |
| 54 | + matrix,matrixSummary:{pairs:matrix.length,strictlyHigherTierPairs:decidedMatrix.length,hierarchyUpsets:hierarchyUpsets.length,upholdRate:decidedMatrix.length?(decidedMatrix.length-hierarchyUpsets.length)/decidedMatrix.length:1,worstUpsets:hierarchyUpsets.slice(0,5)}, |
| 55 | + threeVsThree:{samples:threeVsThree,teamAWins,teamBWins,draws,teamAWinRate:sideBattles?teamAWins/sideBattles:0,teamAWilson95:ci,sideBiasPass:ci.low<=.5&&ci.high>=.5,noInfinite:threeVsThree.every(r=>r.ended&&r.rounds<roundCap)}}; |
| 56 | +const output=path.join(ROOT,'qa/v7-preset-matrix.json'); |
| 57 | +fs.writeFileSync(output,JSON.stringify(artifact,null,2)+'\n'); |
| 58 | +console.log(JSON.stringify({output:path.relative(ROOT,output),matrixPairs:matrix.length,hierarchyUpholdRate:artifact.matrixSummary.upholdRate,threeVsThree:{teamAWins,teamBWins,draws,teamAWinRate:artifact.threeVsThree.teamAWinRate,sideBiasPass:artifact.threeVsThree.sideBiasPass}},null,2)); |
0 commit comments