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

+10 -1
View File
@@ -483,6 +483,15 @@ The numbered sections below keep their full scope. Each one opens with a checkli
- Buena's show at the Radio Tower: `AskRememberPassword`, `BuenasPassword` (the category's three words, B disabled) and `BuenaPrize` (prizes and points beside the Blue Card balance, confirm, pay, receive). Tests: `tests/phone.test.ts` (rematches, a random call, no call without a caller, the daily reset, swarms, each station's lines and music, the radio card, a house radio, Buena's password and prizes) and `tests/radio-browser.spec.ts` (the radio card in the browser).
- Not ported: the radio card's tuning dial graphics (the frequency is shown as text), holding up or down to turn the knob continuously, the Poké Flute channel waking Snorlax (Kanto is closed), and calls that use the mobile adapter.
## Implemented: save slots (board #37)
- `storage/slots.ts`: the native database (`crystal-native-port`) moves to IndexedDB version 2 with `slots` (durable ID, ROM hash, name, created/updated time, newest generation, and a display summary), `records` (keyed by slot and generation) and `meta` (the slot last played per ROM, the migration record). The version-1 `saves` store is copied into "Save 1" inside the upgrade transaction itself, so it happens exactly once (an aborted upgrade retries; a committed one never runs again); the record is copied byte for byte and the old store is left untouched.
- Writes are atomic: one transaction adds generation n+1, drops generations below n and updates the slot. Each record carries a CRC-32 of its fields and state. Loading takes the newest intact record that the game accepts, falling back to the previous one (reported to the player); the next write supersedes the damaged one.
- Versioning: slots carry `version: 1`, records the existing envelope (`version: 1`, `coreId`) and the game state its schema (1-3, migrated by `migrateSave`). A slot whose model or newest record is newer is listed, exportable and deletable, but never opened, copied or written.
- `app/slot-session.ts` (`SlotSession`, native only; the reference keeps `GameSession`): load (last played slot, otherwise the newest readable one, otherwise a new game in a new slot), save into the slot captured with the snapshot (an autosave queued before a switch lands in its own slot), switch, new game, rename (1-24 characters), duplicate, delete (not the running slot), export, and import. Switching, new games and imports save the running game first and restore it if the target cannot be opened. `NativeRuntime.reset()` rebuilds the world from the loaded ROM so nothing carries over between slots.
- Import preview (`previewImport`) checks the envelope, cartridge, player, base64 and the game state in a scratch world (`NativeRuntime.inspectState`) before anything changes, and shows the trainer and ID, place, play time, badges, caught count, saved time, the game data version (and its upgrade) and the slots of the same trainer, marking one with identical progress. The player adds the backup as a new save or replaces the running save or a conflicting one; a replaced slot keeps its previous record.
- Tests: `tests/save-slots.test.ts` (migration once, a newer singleton kept, slot lifecycle, autosave isolation, recovery from a damaged or missing newest record, an interrupted write, rollback when a slot cannot be opened, rejected imports, conflicts and both import targets), `tests/native.test.ts` (`describe` on saves of schema 1, 2 and 3) and `tests/save-slots-browser.spec.ts` (the migration and every Savegames action in Chromium and WebKit).
## Native battles: current state
The battle core, ROM tables, encounter hooks, battle UI and whiteout hooks are present. Unit tests cover wins, escape, loss, experience, move learning and capture; reference tests compare damage, type effectiveness, stat calculation and experience curves. Browser tests drive a test-only wild encounter through the real UI: victory with level-up and move learning, escape, whiteout, catching with a nickname, keyboard and on-screen D-pad menu navigation, mid-battle backup restore, rollback of unsupported effects, switching through the party menu and a Potion from the Items pocket. Real Route 29 encounters now use the ROM's encounter tables. Every ball can be thrown; only the contest's Park Ball handling remains.
@@ -606,7 +615,7 @@ Acceptance: main-game/postgame playthroughs, representative optional content, ba
- [x] Pokédex screen: New mode list and entries
- [x] Scalable text and configurable keyboard controls
- [ ] Fuller accessibility: announcements for every native screen and touch control remapping
- [ ] Save slots with migration, atomic writes and import previews
- [x] Save slots with migration, atomic writes and import previews
- [x] Installable web app with offline app shell, safe updates and quota handling
- [ ] Acceptance on physical iPhone and Android devices