// Import must preserve the running world before reading or replacing it. const fs = require('fs'); const path = require('path'); const { load, suite, flatWorld } = require('./harness'); const { run } = load(), { check, done } = suite(); run(fs.readFileSync(path.join(__dirname, '../src/ui/saves.js'), 'utf8')); run(` globalThis.GAME = { running: true, paused: true, worldId: 'current', worldName: 'Current', playTime: 42 }; globalThis.messages = []; globalThis.toast = text => messages.push(text); globalThis.$ = () => ({}); globalThis.showLoading = () => {}; globalThis.stored = new Map(); globalThis.failStorage = false; globalThis.releaseWrite = null; DB.get = async key => stored.get(key); DB.op = async (mode, fn) => { if (failStorage) throw new Error('Storage full'); await new Promise(resolve => { releaseWrite = resolve; }); fn({ put: (value, key) => stored.set(key, value) }); }; globalThis.loads = 0; globalThis.startFromSave = async sv => { loads++; deserializeWorld(sv.meta, sv.buffers); GAME.worldId = sv.worldId; GAME.worldName = sv.worldName; return true; }; `); async function main() { flatWorld(run, 64, 64, 48); const incoming = run('serializeWorld()'); const buffers = {}; for (const [key, arr] of Object.entries(incoming.buffers)) { buffers[key] = { type: arr.constructor.name, gz: false, b64: Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString('base64') }; } const text = JSON.stringify({ format: 'deepgrain-world', meta: incoming.meta, buffers }); run('money = 12345; setCell(10 * W + 10, CUORE, AMB[10], 0);'); let reads = 0, savedBeforeRead = false; // Release each storage transaction on the next turn, after observing whether // the importer tried to read the file while the first save was still pending. const pending = run('importWorldFile')({ text: async () => { reads++; savedBeforeRead = run("stored.has('current:auto')"); return text; } }); await new Promise(resolve => setImmediate(resolve)); check('file waits for the running world save to commit', reads === 0); let settled = false; pending.finally(() => { settled = true; }); const deadline = Date.now() + 10000; while (!settled && Date.now() < deadline) { if (run('releaseWrite !== null')) run('releaseWrite(); releaseWrite = null;'); await new Promise(resolve => setTimeout(resolve, 1)); } if (!settled) throw new Error('Import timed out'); await pending; check('current world is saved before reading the import', savedBeforeRead); check('autosave contains latest money and grains', run(`(() => { const sv = stored.get('current:auto'); return !!sv && sv.meta.economy.money === 12345; })()`) && (await run("worldNewest('current')"))?.buffers.mat[650] === run('CUORE')); check('good import loads and saves under its own id', run(`loads === 1 && GAME.worldId !== 'current' && stored.has(GAME.worldId + ':auto') && messages.includes('World imported.')`)); run('failStorage = true; messages.length = 0; loads = 0;'); const before = run('mat'); reads = 0; await run('importWorldFile')({ text: async () => { reads++; return text; } }); check('failed save stops before reading or replacing the world', reads === 0 && run('loads') === 0 && before === run('mat')); check('failed save explains that the current world could not be saved', run("messages.some(s => /current world/i.test(s) && /could not be saved/i.test(s))")); // Title-screen import must not attempt to save a nonexistent running world. run('GAME.running = false; messages.length = 0; loads = 0;'); reads = 0; await run('importWorldFile')({ text: async () => { reads++; return text; } }); check('title-screen import still reads and loads without a pre-save', reads === 1 && run('loads') === 1); done(); } main().catch(e => { console.error(e); process.exitCode = 1; });