'use strict'; /* Headless smoke test: runs the engine without a browser. */ const fs=require('fs'),path=require('path'),vm=require('vm'); const ctx={console,Math,Date,JSON,Object,Array,Set,Map,Number,String,Boolean,RegExp,parseInt,parseFloat,isFinite,NaN,Infinity,undefined}; ctx.globalThis=ctx; vm.createContext(ctx); const dir=path.join(__dirname,'..','js'); for(const f of ['util.js','data.js','game.js','match.js']){ vm.runInContext(fs.readFileSync(path.join(dir,f),'utf8'),ctx,{filename:f}); } const t=(name,cond)=>{if(!cond){console.error('❌ FAIL:',name);process.exitCode=1;}else console.log('✔',name);}; let out=vm.runInContext(` (function(){ const log=[]; Game.newWorld('normal'); log.push('teams='+Object.keys(G.teams).length+' players='+Object.keys(G.players).length+' staffPool='+Object.keys(G.staff).length+' events='+G.calendar.length); // sanity: every team has 5-man lineup let badLineup=[...Object.values(G.teams)].filter(t=>t.lineup.length!==5).map(t=>t.name); log.push('badLineups='+JSON.stringify(badLineup)); Game.startWithTeam('t0'); // NAVI const meId=G.playerTeamId; // register everything G.calendar.forEach(e=>{const r=Game.registerEvent(e.id);}); // fast-forward two years let userMatches=0,eventsDone=0,champs={},errors=[]; for(let i=0;i<760;i++){ try{ const fx=Game.advanceOneDay(); if(fx){ userMatches++; const ev=fx.evId?G.calendar.find(e=>e.id===fx.evId):null; MatchEngine.simulate(fx,ev); } }catch(e){errors.push(i+':'+e.stack.split(String.fromCharCode(10)).slice(0,3).join(' | '));} } eventsDone=G.calendar.filter(e=>e.status==='done').length; G.calendar.forEach(e=>{if(e.champion)champs[e.name]=Game.team(e.champion).tag;}); const me=Game.team(meId); log.push('userMatches='+userMatches+' eventsDone='+eventsDone); log.push('myRank=#'+(G.ranking.indexOf(meId)+1)+' pts='+Math.round(me.ratingPts)+' money='+Math.round(G.money)+' fans='+G.fans+' board='+Math.round(G.board)); log.push('season='+G.season+' newsCount='+G.news.length); log.push('champSample='+JSON.stringify(Object.fromEntries(Object.entries(champs).slice(0,6)))); log.push('errors='+JSON.stringify(errors)); // stats sanity for star player const star=me.lineup.map(id=>G.players[id]).filter(Boolean).sort((a,b)=>b.seasonStats.maps-a.seasonStats.maps)[0]; log.push('star='+(star?star.nick+' maps='+star.seasonStats.maps+' rating='+(star.seasonStats.maps?(star.seasonStats.ratingSum/star.seasonStats.maps).toFixed(2):'n/a'):'none (empty lineup)')); // transfer mechanics const faId=G.freeAgents[0]; G.money=5000000; const r1=Game.signFreeAgent(faId,wageDemand(Game.p(faId),G)*1.2,3,100000); log.push('signFA='+r1.ok+':'+r1.msg.slice(0,40)); // interactive session test vs weakest AI team const weakest=Object.values(G.teams).filter(x=>x.id!==meId).sort((a,b)=>a.ratingPts-b.ratingPts)[0]; const fx={id:'testfx',a:meId,b:weakest.id,bo:3,day:G.day}; const before=me.ratingPts; const S=SimSession.start(fx,null); let guard=0; while(S.phase==='veto'||S.phase==='pregame'){SimSession.vetoAction([...S.veto.pool][0]);if(guard++>20)break;} SimSession.beginMap(0); guard=0; while(S.phase!=='done'&&guard++<800){ if(S.phase==='intermission')SimSession.beginMap(S.results.length); else if(S.awaiting==='buy')SimSession.chooseBuy(S.cur.fund[S.userId]>=11800?'full':'eco'); else if(S.awaiting==='tactic')SimSession.chooseTactic(['aggr','default','patient','fake'][guard%4]); else if(S.awaiting==='next')SimSession.nextRound(); } log.push('simMode: phase='+S.phase+' score='+S.scoreA+'-'+S.scoreB+' maps='+S.results.map(r=>r.map+' '+r.sa+'-'+r.sb).join(', ')); // save/load round-trip const snap=Game.serialize(); Game.staticLoad(snap); log.push('saveRoundTrip='+(G.playerTeamId===meId&&Object.keys(G.players).length>100?'OK':'BROKEN')); return log.join('\\n'); })() `,ctx); console.log(out);