Never strand a player: handling old and broken saves in a mobile game
After a big update to Moonlit Garden, the pipe puzzle, I opened the game on my phone to carry on a half-played garden. The play screen was empty. No board, no buttons, nothing to tap.
All the tests had passed.
What happened
The update added coloured lanterns, so each saved garden gained a color array describing which light reaches which flower. My half-played garden was saved by the previous version and had no color array. The renderer read the save, found nothing to draw, and drew nothing.
The tests passed because every test started from a fresh save. None of them loaded a garden written by an older version of the game. The bug was invisible to every check I had, and obvious to the first real player who updated.
The rule
My rule for this now is short: if a saved level is invalid, restart it. Don't strand the player.
In practice:
- every saved puzzle is checked with a
playable()function when it loads, not just parsed, - if it fails, for whatever reason (old format, damaged data, a level whose definition changed), the game rebuilds that level from its seed,
- the player's progress is kept: their level number, stars, streaks, unlocks. Only the in-progress board is lost.
Losing a half-solved puzzle is mildly annoying. A blank screen with no way out is an uninstall.
Because levels are generated from seeds (the same trick the Word Search engine uses), restarting a level is always possible. That's one more argument for seeded content: there's always a known-good way to rebuild any state.
Test with old saves, deliberately
The flow test now loads an old-format save and a deliberately damaged one on every run, and checks the player lands on a playable board with their progress intact. Any game that changes its save format adds a fixture from the old version.
The same idea shows up across the portfolio:
- FreeCell saves its undo history with the game, and a test checks undo still works after leaving and reloading.
- My idle game runs every save through a double round-trip: load, save, load, save. If the second save differs from the first, something migrated twice. That once wiped players' progress on every challenge exit.
- Idle Fishing Empire had the opposite bug: "Start over" didn't. The page reload fired a visibility event whose handler saved the in-memory game right back. Wipes now disable every save path first.
The general lesson
Most game testing starts from zero: new install, empty save, tutorial. Real players almost never are at zero. They're halfway through something, on a version you shipped weeks ago. If your test suite has no saves from the past, it's testing a player who doesn't exist.