Files
warlords-fate/tools/smoke.mjs
T
deepseek f040bb6be0 Three Kingdoms: Warlord's Fate — complete playable game
- Stylized 3D ink-painting map of China (12 provinces, 32 cities, 17 factions)
- Custom warlord creation (8 origins, banner, starting city) or historical factions
- City management: 7 buildings, 5 dev tiers, recruitment from levies
- Character system: stats, traits, loyalty, relationships, wounds, capture, death, succession
- Turn-based tactical battles with formations, stances, hero skills, cinematic 3D replay
- Sieges: assault, starvation, bribery, infiltration
- Diplomacy with trust memory, alliances, NAPs, trade, marriage, espionage, betrayal
- Scripted diverging history (Dong Zhuo, Guandu, Red Cliffs...) + world crises + court events
- AI factions with distinct personalities; prisoners (execute/release/recruit/ransom)
- Procedural guqin/taiko WebAudio score; save/load; victory + dynasty chronicle screens
- View-relative camera controls; headless test suites (smoke, stress, map validator)
2026-08-23 06:59:40 +00:00

75 lines
3.0 KiB
JavaScript

// 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");