Counter-Strike esports manager: career mode + LIVE tactical top-down simulation
- Full manager sim: 28 orgs / 168 players, calendar, transfers, youth scouting, loans, training, board confidence - Simulation Mode with real-time micro-engine (live.js): A* pathfinding on map geometry, raycast ballistics (spread/falloff/armor/headshots), smokes/flashes/HE that matter, plant/defuse/retake AI, clutch detection - Canvas renderer (viz.js): follow/radar cameras, HP bars, CS2-style kill feed, callouts, synthesized Web-Audio SFX, speed x1-x3, AUTO-SPECTATE hands-free series playback - Round results flow from the firefight back into the career engine (score, economy, box score, MVP) - Playwright + Node VM test suites included (test/, plus external pwtest harness)
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
'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);
|
||||
Reference in New Issue
Block a user