Import cartridge .sav files with verified structure, checksums and clock (Closes #44)
Savegames → Import backup now reads an English Crystal .sav (from an emulator, a cartridge dump or the 3DS Virtual Console's sav.dat) and converts it into a native save after a preview. - world/cartridge-save.ts: picks the copy TryLoadSaveFile would load (check values and checksums of the main copy, else the backup), reads the saved WRAM block, the numbered boxes, mail backups, Hall of Fame and Battle Tower by PRET labels (SRAM, SAVE_BLOCK in the profile, checked by check:reference with the SAV constants), engine flags via EngineFlags, scenes via MapScenes, and the map's objects and masks. - Clock: an emulator MBC3 footer plus the time since it was written, FixDays/FixTime with wStart*, mapped to the minute and weekday offsets; without one, the device's time and the saved wCurDay. Day countdowns move with the day difference. - Refused with the reason: unported maps, the Bug-Catching Contest, a Battle Tower challenge, characters outside the naming screen. - Names keep <PK>/<MN> as ᴾᴷ/ᴹᴺ (one character, one tile); trainer names accept the naming screen's alphabet; boxed Eggs validate; native states up to 256 kB load (a full PC exceeded 64 kB). - Tests: the committed debug saves, layout offsets against the symbols, backup fallback, clock footers, day counters, refusals, slot session and the Savegames flow in Chromium and WebKit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
87b05b45f4
commit
592ca43a40
20 files changed
+1821
-40
No files matched your search
@@ -39,7 +39,7 @@ flowchart LR
|
||||
|
||||
Saves made during an unfinished walk or jump retain its source tile and completed step count, so loading cannot skip a landing event or map transition. Directional mats wait for exit input; doors, stairs and caves force an animated downward step after arrival.
|
||||
|
||||
Native and reference sessions use different IndexedDB databases and Web Locks. The native player keeps save slots (`storage/slots.ts`, `app/slot-session.ts`): each slot keeps its newest two records with checksums, written in one transaction, and loading falls back to the previous record when the newest is damaged. The singleton save of database version 1 becomes the first slot inside the version-2 upgrade. Imports are previewed in a scratch world and reject incompatible schemas and invalid positions before changing the world or any slot. A failed import, switch or new game restores the prior state. Original cartridge saves and emulator snapshots cannot be used as native saves.
|
||||
Native and reference sessions use different IndexedDB databases and Web Locks. The native player keeps save slots (`storage/slots.ts`, `app/slot-session.ts`): each slot keeps its newest two records with checksums, written in one transaction, and loading falls back to the previous record when the newest is damaged. The singleton save of database version 1 becomes the first slot inside the version-2 upgrade. Imports are previewed in a scratch world and reject incompatible schemas and invalid positions before changing the world or any slot. A failed import, switch or new game restores the prior state. Emulator snapshots cannot be used as native saves. Cartridge saves are converted, not loaded: `world/cartridge-save.ts` checks the size, the check values and checksums of both copies (taking the backup when the main copy fails, as `TryLoadSaveFile` does), reads the saved WRAM block, the numbered PC boxes, the mail backups, the Hall of Fame and the Battle Tower by PRET addresses (`SAVE_BLOCK`, `SRAM` in the profile), translates engine flags through the ROM's `EngineFlags` and scenes through `MapScenes`, and turns the cartridge clock (an emulator's MBC3 footer plus `wStart*`, as `FixDays`/`FixTime` do) into the port's minute and weekday offsets, moving stored day counters by the same amount. The result goes through the same preview and `restore` checks as a native backup.
|
||||
|
||||
`GameWorld` advances in overworld ticks of two video frames, as the original loop does. The runtime converts elapsed time to ticks. Tile events follow `CheckTileEvent`: a completed step checks connections, then warps, then coordinate events for the current scene. Idle ticks run the current scene script. Map entry runs the NEWMAP, TILES and OBJECTS callbacks on a copy first, so an unsupported callback refuses the step instead of entering a half-initialized map. Objects spawn from event flags at map load, and later flag changes leave them in place until `appear`/`disappear` or the next load.
|
||||
|
||||
|
||||
@@ -120,6 +120,8 @@ Saves live in slots (see [PLAN.md](PLAN.md), save slots): the slot model is vers
|
||||
|
||||
Native saves are version 3. Version 2 added scene values by owning map, phone contacts, the weekday offset and DST flag, and positions of objects that scripts moved on the current map. Version 3 adds the player ID, party, bag, Pokédex seen/caught sets and money, and later Mom's savings (`momsMoney`, migrated by absence to 0). Early version-3 backups without money receive the original starting balance; explicitly invalid balances are rejected. Restores recompute each Pokémon's stats from its DVs and reject any mismatch, and check every item against its ROM pocket. Older saves migrate with new-game defaults; a missing player ID is drawn as a new game draws it. Objects are restored from the save on continue, and only the TILES callback runs, as in `MapSetupScript_Continue`.
|
||||
|
||||
Cartridge `.sav` files are converted into this schema rather than loaded (`world/cartridge-save.ts`; see [PLAN.md](PLAN.md), cartridge save import). Player and OT names accept the naming screen's alphabet, with <PK> and <MN> stored as `ᴾᴷ` and `ᴹᴺ`, each one character.
|
||||
|
||||
Later fields keep, by absence-migrated defaults: `memory`, the saved WRAM bytes that scripts read and write directly, by PRET label (`SAVED_WRAM`); and `unownDex` with `firstUnownSeen` (`wUnownDex`, `wFirstUnownSeen`).
|
||||
|
||||
## Scripted trainer battle foundation
|
||||
|
||||
+11
-1
@@ -519,6 +519,15 @@ The numbered sections below keep their full scope. Each one opens with a checkli
|
||||
- `PokemonCenterPC.ChooseWhichPCListToUse` picks `PCPC_POSTGAME` from the count, as the original does, so HALL OF FAME appears once a team is recorded. `_HallOfFamePC`, `LoadHOFTeam` and `DisplayHOFMon` show each Pokémon's page with its cry: A shows the next Pokémon, then the next team; Start skips to the next team; B returns to the PC. The original's unreachable "HOF Master!" title (it needs a count of 201) is kept.
|
||||
- `tests/reference-hall-of-fame.spec.ts` writes 255 teams (every species, Unown, level 100, the Nidorans, win 200, all eight frames) into the cartridge's SRAM, runs its `_HallOfFamePC`, and compares the tilemap, attributes, graphics and palette with the port's page. `HOF_AnimateFrontpic` is the one routine stubbed: the port shows the picture at rest, without the `ANIM_MON_HOF` animation. `tests/hall-of-fame.test.ts` covers the records, the PC menu with and without them, browsing, and save, reload and migration.
|
||||
|
||||
## Implemented: cartridge save import (board #44)
|
||||
|
||||
- `world/cartridge-save.ts` converts a 32 KiB English Crystal `.sav` (optionally followed by a 44- or 48-byte emulator clock footer, or the Virtual Console's 16 bytes, which are not read) into a version-3 native save. Every SRAM and WRAM address it reads is keyed by its PRET label in `profile.ts` (`SRAM`, `SAVE_BLOCK`) and checked by `npm run check:reference`, like its PRET constants (`SAV`); `tests/cartridge-save.test.ts` compares its struct offsets (`LAYOUT`) with the pinned build's symbols.
|
||||
- Copy selection follows `TryLoadSaveFile`: check values and the 16-bit checksum of the main copy, else the backup's; both damaged, or never saved, is refused. Boxes come from their own banks (`LoadBox` copies the current one into `sBox`), party mail and the mailbox from their backups (`RestorePartyMonMail`), the Battle Tower's received reward becomes a won challenge (`LoadPlayerData`).
|
||||
- The clock: with an emulator footer, the MBC3 registers plus the time since the file was written (unless halted), nine-bit days modulo 140 and `wStart*` added with carries; without one, the device's time and the saved `wCurDay`. The minute and weekday offsets reproduce that time and weekday; the Lucky Number countdown, its drawn day and the daily reset move with the difference between `wCurDay` and the port's day number.
|
||||
- Names keep the naming screen's alphabet, with <PK> and <MN> as `ᴾᴷ`/`ᴹᴺ` (one character, one tile); player and OT names now accept that alphabet. Boxed Eggs validate (they keep their `egg` mark). Native states up to 256 kB load, enough for a full PC.
|
||||
- Refused with the reason: unported maps, the Bug-Catching Contest, a Battle Tower challenge in progress, characters outside the naming screen (traded Japanese names, ligatures). Not carried over, with a note in the preview: the tiles walked count, Mystery Gift items, link battle records; time-windowed objects follow the clock again.
|
||||
- Savegames → Import backup reads `.sav`/`.srm`/`.dat` files (or any 32 KiB non-JSON file), previews trainer, place, progress, which copy and clock were used and the notes, then adds or replaces a save like a native backup (`SlotSession.previewCartridge`). Tests: `tests/cartridge-save.test.ts` (the committed real `.sav` and Virtual Console `sav.dat`, backup fallback, clock footers, day counters, refusals), `tests/save-slots.test.ts` and `tests/save-slots-browser.spec.ts`.
|
||||
|
||||
## 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.
|
||||
@@ -643,6 +652,7 @@ Acceptance: main-game/postgame playthroughs, representative optional content, ba
|
||||
- [x] Scalable text and configurable keyboard controls
|
||||
- [ ] Fuller accessibility: announcements for every native screen and touch control remapping
|
||||
- [x] Save slots with migration, atomic writes and import previews
|
||||
- [x] Cartridge `.sav` import with verified structure, checksums and clock conversion (board #44)
|
||||
- [x] Installable web app with offline app shell, safe updates and quota handling
|
||||
- [ ] Acceptance on physical iPhone and Android devices
|
||||
|
||||
@@ -656,7 +666,7 @@ Dependencies: stable serialization for every system stored in a slot, migration
|
||||
|
||||
Acceptance: migrate existing browser data without loss, keep several independent playthroughs, recover from interrupted writes and reject incompatible or corrupt imports without changing a valid slot. Test create/load/delete/rename/duplicate, autosave isolation, export/import conflicts and upgrades from every released schema.
|
||||
|
||||
Add PWA installation/offline caching of the app shell only. ROM/data retention remains a separate explicit local preference; otherwise reselect the ROM after reopening. Add safe application updates, schema migrations, storage persistence/quota handling and portable backups. Investigate original `.sav` import only with verified structure, checksums and RTC conversion.
|
||||
Add PWA installation/offline caching of the app shell only. ROM/data retention remains a separate explicit local preference; otherwise reselect the ROM after reopening. Add safe application updates, schema migrations, storage persistence/quota handling and portable backups. Original `.sav` files import through a converter with verified structure, checksums and RTC conversion (board #44, below).
|
||||
|
||||
Acceptance: installation, offline startup, portrait/landscape, touch/audio, background/resume and updates on physical iPhone/Safari and Android/Chrome. Browser automation does not establish physical-device compatibility.
|
||||
|
||||
|
||||
Reference in new issue
Block a user