/** * Lockstep determinism check: two engines receiving the same actions at the * same ticks must produce identical game state (the foundation of multiplayer * without state sync). */ import { GameEngine } from '../src/game/engine.ts' const e1 = new GameEngine() const e2 = new GameEngine() for (const e of [e1, e2]) { e.startGame('normal') e.mpGameActive = true e.mpMode = 'coop' e.coopHpMul = 1.7 } // scripted action stream (tick, action) – like the relayed server order const script: [number, Parameters[0]][] = [ [5, { type: 'build', kind: 'arrow', tx: 8, ty: 5 }], [5, { type: 'build', kind: 'frost', tx: 3, ty: 0 }], [10, { type: 'wave' }], [200, { type: 'build', kind: 'cannon', tx: 7, ty: 0 }], [400, { type: 'upgrade', towerId: 1 }], [400, { type: 'build', kind: 'tesla', tx: 12, ty: 6 }], [700, { type: 'wave' }], [900, { type: 'obstacle', tx: 19, ty: 0 }], [1200, { type: 'build', kind: 'laser', tx: 18, ty: 4 }], [1500, { type: 'speed', s: 2 }], [1800, { type: 'wave' }], ] let idx = 0 const TOTAL_TICKS = 3600 // ~2 Minuten Spielzeit @30 Ticks/s let p0: ReturnType = true let p1 = p0 for (let tick = 1; tick <= TOTAL_TICKS; tick++) { while (idx < script.length && script[idx][0] <= tick) { const a = script[idx][1] p0 = e1.applyAction(a, idx % 2) // alternate acting players p1 = e2.applyAction(a, idx % 2) idx++ } e1.update(1 / 30) e2.update(1 / 30) } function state(e: GameEngine): string { return JSON.stringify({ money: Math.floor(e.money * 1000), lives: e.lives, score: e.score, kills: e.kills, wave: e.waveNo, phase: e.phase, enemies: e.enemies.map((x) => [x.id, Math.round(x.hp * 1000), x.wp]), towers: e.towers.map((t) => [t.id, t.kind, t.level, t.kills, Math.round(t.damageDealt)]), queue: e.spawnQueue.map((s) => [Math.round(s.t * 1000), s.kind]), blocked: e.blockedCells.size, }) } const s1 = state(e1) const s2 = state(e2) console.log('Aktionen ausgeführt:', idx, '| identisch:', p0 === p1) console.log('Zustand identisch:', s1 === s2) if (s1 !== s2) { console.log('e1:', s1.slice(0, 400)) console.log('e2:', s2.slice(0, 400)) process.exit(1) } console.log(`OK: beide Engines identisch nach ${TOTAL_TICKS} Ticks | Welle ${e1.waveNo} | ❤ ${e1.lives} | 🪙 ${Math.floor(e1.money)} | Türme ${e1.towers.length}`)