import { test } from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { Engine } from "../src/core/engine.js"; import { SAVE_VERSION } from "../src/core/save.js"; const example = JSON.parse(readFileSync(new URL("../example/game.json", import.meta.url), "utf8")); const fresh = () => { let now = 0; const e = new Engine(structuredClone(example), { clock: () => now }); e.newGame(); return { e, advance: (ms) => { now += ms; e.tick(ms); } }; }; test("save/load round-trip restores the whole world", () => { const { e, advance } = fresh(); e.selectVerb("use"); e.click({ kind: "object", id: "mug", tags: ["container"] }); e.goTo("tavern"); e.world.setFlag("didSomething", true); e.world.addVar("score", 42); const save = e.save(); const e2 = new Engine(structuredClone(example), { clock: () => 0 }); e2.load(save); assert.equal(e2.world.room, "tavern"); assert.deepEqual(e2.world.inventory, ["mug"]); assert.equal(e2.world.flags.didSomething, true); assert.equal(e2.world.vars.score, 42); assert.equal(e2.phase, "playing"); }); test("save format is versioned and game-tagged", () => { const { e } = fresh(); const data = JSON.parse(e.save()); assert.equal(data.version, SAVE_VERSION); assert.equal(data.game, "skeleton-demo"); assert.ok(data.savedAt); }); test("rejects saves from another game", () => { const { e } = fresh(); const save = e.save(); const other = structuredClone(example); other.meta.name = "different-game"; const e2 = new Engine(other, { clock: () => 0 }); assert.throws(() => e2.load(save), /save is for game/); }); test("rejects future save versions", () => { const { e } = fresh(); const data = JSON.parse(e.save()); data.version = 999; assert.throws(() => e.load(JSON.stringify(data)), /newer than engine/); }); test("rejects garbage saves", () => { const { e } = fresh(); assert.throws(() => e.load("not json"), /not valid JSON/); assert.throws(() => e.load("{}"), /no version/); }); test("mid-dialogue save resumes at the saved node", () => { const { e } = fresh(); e.goTo("tavern"); e.selectVerb("talk"); e.click({ kind: "character", id: "barkeep", tags: ["talker"] }); assert.equal(e.dialogue.current().node, "greet"); const save = e.save(); const e2 = new Engine(structuredClone(example), { clock: () => 0 }); e2.load(save); assert.ok(e2.dialogue.active); assert.equal(e2.dialogue.current().node, "greet"); assert.equal(e2.dialogueChoices().length, 2); });