// Headless smoke test: create a game, simulate many turns. const shim = new Map(); globalThis.localStorage = { getItem: k => shim.get(k) ?? null, setItem: (k, v) => shim.set(k, String(v)), removeItem: k => shim.delete(k), get length() { return shim.size; }, key: i => [...shim.keys()][i] ?? null, }; import { newGame, G, F, saveGame, loadGame, listSaves } from "../js/state.js"; import { endTurn, recruitToGarrison } from "../js/sim.js"; import { factionTroopCount } from "../js/state.js"; import { applyEffect } from "../js/events.js"; const opts = { mode: "campaign", difficulty: "normal", playerType: "custom", custom: { rulerName: "Minh", color: "#c2452d", emblem: "⚔", origin: "governor", cityId: "xuchang", factionName: "" }, }; let g = newGame(opts); console.log("factions:", Object.keys(g.factions).length); console.log("generals:", Object.keys(g.generals).length); console.log("cities:", Object.keys(g.cities).length); const pf = F("player"); console.log("player cities:", pf.cities, "gold:", pf.gold, "gens:", Object.values(g.generals).filter(x => x.faction === "player").map(x => x.name)); // sanity: leaders assigned for (const f of Object.values(g.factions)) { if (f.id === "neutral") continue; if (!f.leader) console.log(`!! faction ${f.id} has no leader`); } // resolve any pending events each turn by choosing first option function drainEvents() { let guard = 0; while (g.pendingEvents.length && guard++ < 50) { const ev = g.pendingEvents.shift(); const choice = ev.choices[0]; try { applyEffect(choice.effect); } catch (e) { console.log("EFFECT ERR", ev.title, e.message); } } } let battles = 0, errors = []; for (let t = 0; t < 60; t++) { try { // simple player activity: recruit sometimes if (t % 3 === 0) { const c = g.cities[pf.cities[0]]; if (c) recruitToGarrison(c, "spear", Math.min(c.levies, 200)); } endTurn(); battles += g.replays.filter(r => r.rounds?.length || r.kind === "surrender").length; drainEvents(); } catch (e) { errors.push(`turn ${t}: ${e.stack.split("\n").slice(0, 3).join(" | ")}`); break; } } console.log("\nafter 60 turns => year", g.year, "/", g.month); for (const f of Object.values(g.factions)) { if (!f.alive) { console.log(` ✝ ${f.name} destroyed`); continue; } console.log(` ${f.name.padEnd(16)} cities:${String(f.cities.length).padStart(2)} gold:${String(f.gold).padStart(6)} food:${String(f.food).padStart(6)} troops:${factionTroopCount(f.id)} legit:${f.legitimacy} rank:${f.rank}`); } console.log("battles fought:", battles); console.log("journal entries:", g.journal.length); console.log("chronicle:", g.chronicle.slice(0, 6).map(c => `${c.y}/${c.m} ${c.text}`)); if (errors.length) { console.log("ERRORS:\n" + errors.join("\n")); process.exit(1); } // save/load roundtrip saveGame("test"); const saves = listSaves(); console.log("saves:", saves.map(s => s.slot)); const loaded = loadGame("test"); console.log("loaded ok:", loaded.year === g.year && loaded.playerFaction === "player"); // battle sanity console.log("\nSMOKE TEST PASSED");