Keep several saves, with migration, atomic writes and import previews (Closes #37)
Checks / web (push) Successful in 2m33s
Private ROM checks / private-rom (push) Successful in 20m32s

Savegames now lists independent save slots made with the loaded ROM:
new game, continue, rename, duplicate, export and delete. The native
database moves to IndexedDB version 2; the singleton save becomes
"Save 1" inside the upgrade transaction, so it migrates exactly once and
the old store is left untouched. Each write adds a generation and drops
older ones in one transaction, keeping two records with CRC-32
checksums: a damaged newest record falls back to the previous one.
Slots and records from a newer player are kept and never written.

Imports are checked in a scratch world before anything changes and
previewed with the trainer, progress, saved time, game data version and
the slots of the same trainer; they go into a new save or replace one,
whose previous record stays recoverable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kawaandClaude Opus 5.5 committed 2026-10-16 11:27:41 +02:00
1 parent 7c12c81a27
commit 7d03f5c5ca
18 files changed
+2079 -56

No files matched your search

+5 -5
View File
@@ -120,8 +120,8 @@ test("local maps follow a stairs transition and importing a previous position",
`${world.map.name} · ${world.map.location}`,
);
await page.locator("#tab-saves").click();
page.once("dialog", (dialog) => dialog.accept());
await page.locator("#save-file").setInputFiles((await backup.path())!);
await page.locator("#import-current").click();
await expect(page.locator("#status")).toContainText("Backup restored");
await page.locator("#tab-map").click();
await expect(page.locator("#world-map")).toHaveAttribute("data-map", "24:7");
@@ -239,8 +239,8 @@ test("choosing a starter shows its picture, asks for a nickname and saves the pa
await page.locator("#rom-file").setInputFiles(romPath);
await expect(page.locator("#status")).toContainText("verified");
await page.locator("#tab-saves").click();
page.once("dialog", (dialog) => dialog.accept());
await page.locator("#save-file").setInputFiles(backup);
await page.locator("#import-current").click();
await expect(page.locator("#status")).toContainText("Backup restored");
await page.locator("#play").click();
await page.keyboard.press("KeyX");
@@ -333,8 +333,8 @@ test("a home exit mat survives save/reload and waits for the exit direction", as
await page.locator("#rom-file").setInputFiles(romPath);
await expect(page.locator("#status")).toContainText("verified");
await page.locator("#tab-saves").click();
page.once("dialog", (dialog) => dialog.accept());
await page.locator("#save-file").setInputFiles(backup);
await page.locator("#import-current").click();
await expect(page.locator("#status")).toContainText("Backup restored");
await page.locator("#play").click();
const screen = page.locator("#game-screen");
@@ -409,8 +409,8 @@ test("walks from New Bark Town onto Route 29 after receiving a starter", async (
await page.locator("#rom-file").setInputFiles(romPath);
await expect(page.locator("#status")).toContainText("verified");
await page.locator("#tab-saves").click();
page.once("dialog", (dialog) => dialog.accept());
await page.locator("#save-file").setInputFiles(backup);
await page.locator("#import-current").click();
await expect(page.locator("#status")).toContainText("Backup restored");
await page.locator("#play").click();
const screen = page.locator("#game-screen");
@@ -496,8 +496,8 @@ test("native player loads the actual ROM without loading an emulator and persist
const exported = page.waitForEvent("download");
await page.locator("#backup").click();
const backup = await exported;
page.once("dialog", (dialog) => dialog.accept());
await page.locator("#save-file").setInputFiles((await backup.path())!);
await page.locator("#import-current").click();
await expect(page.locator("#status")).toContainText("Backup restored");
await page.reload();
await page.locator("#rom-file").setInputFiles(romPath);