mobilegamedevcloudregression

Cloud Save in a mobile game — a testing checklist

A player finishes the first 30 levels on an iPad at work. In the evening they launch the game on their iPhone — no progress. They reinstall — nothing. They write to support and leave a one-star review. This is the worst possible user experience in a mobile game, and the culprit is almost always an untested cloud save.

Cloud save sounds simple — write a JSON blob to the cloud, read it on another device. In practice it’s 5+ systems that fail in different ways: platform APIs don’t behave the way the docs say; conflicts resolve in non-obvious ways; cross-platform migration nearly always loses purchases; the save file can arrive corrupted, and the player has no backup.

This guide is what to actually test in a mobile game’s cloud save.

1. Platform APIs: three approaches

iCloud (Apple). Two flavours:

  • NSUbiquitousKeyValueStore — 1 MB limit per app, total. Fits only very compact saves or metadata (last level cleared, coin count). See Apple docs.
  • CloudKit — a real database, up to 10 GB in the user’s free tier. Supports conflict resolution via CKRecord and change tokens. For a normal game save — this is the only sensible option. See CloudKit docs.

Google Play Games Services — Saved Games. Up to 3 MB per snapshot, up to 4 snapshots per game (use them as backup slots). Conflicts are handled by the developer through SnapshotConflictResolutionPolicy. See the Saved Games guide.

Your own backend (Firebase / custom API). Full control, but more scenarios to test. The default choice for cross-platform games.

What to test:

  • Save succeeds on a slow network (>2 s latency) and after a reconnect.
  • API size limits: what happens when the save grows to 4 MB on Google Play Games — silent fail or an error?
  • Rate limits — Apple and Google dislike writes every 5 seconds.
  • Behaviour when iCloud or the Google account is signed out or disabled in system settings.

2. Conflict between devices

The most common bug — a player plays on two devices at once (iPad on the charger + iPhone on the go), both write to the cloud. What do you merge?

Resolution strategies:

  • Last-write-wins — simple, but loses progress from one of the devices. Only suitable for linear-progress games.
  • Highest-progress-wins — compare by level, score, or timestamp of the last event. The player doesn’t lose progress, but can lose purchases from the “rolled-back” session.
  • Merge logic — combine field by field (currencies — max, unlocked levels — union, inventory — union). Harder, but safer.

Test cases:

  • Device A finishes level 10, device B buys an item in parallel. Launch both in turn — the player should lose neither the level nor the purchase.
  • Device A is offline for 3 days and accumulates progress. Device B plays online the whole time. Device A comes online — who wins?
  • Simultaneous logout/login on two devices within 1 second.
  • Conflict-resolution UI: do you let the player pick “keep version A / keep version B” or resolve it silently? If you show it — the UI must make sense to a player, not be a JSON dump.

3. Cross-platform migration iOS ↔ Android

The most painful area. The player bought premium currency on iOS, moved to Android — where’s the money?

Carries over via cloud save (regardless of store):

  • Level progress, unlocked content, customisations, earned resources.
  • In-game currency earned by the player (not bought with real money).

Does NOT carry over automatically:

  • Currency bought with real money — IAP is tied to the Apple ID and the Google account. If the player bought on iOS and moved to Android, Google has no record of the purchase. Fix: a server-side wallet (currency lives on the backend, the IAP receipt is verified and tops up the cloud wallet, not the local one).
  • Active subscriptions — even technically you can’t move them across stores due to store policies.

Test cases:

  • iOS → Android: create progress + an IAP on iOS, sign in with the same account (Apple ID, Google Sign-In, Facebook, custom) on Android. Verify: progress is present, IAP currency is present (if a server-side wallet is used), the active subscription is correctly shown as “purchased on another platform”.
  • Android → iOS — same.
  • A player without a linked account (playing as guest) moves to a new device — progress should be offered for recovery via account linking.

4. Save corruption and backup slots

A save can arrive corrupt: the network dropped mid-write, the disk filled up, the app OOM’d during serialisation, a new client version writes invalid JSON. What then?

Defenses:

  • A checksum (SHA-256 or at least CRC32) in the save header — verified on read.
  • Atomic writes — write to save.tmp first, then rename to save.json. An interrupted write won’t leave a corrupt file.
  • Multiple slots (Google Play Games gives you 4 snapshots — use them as a rolling backup).
  • Versioning — every save carries a schema_version so a new client knows how to read an old format.

Test cases:

  • Force-feed the game a corrupt JSON (by swapping the file on the device) — the app must not crash, must recover from the backup.
  • Interrupt the save mid-write (kill the process, cut power) — the next launch must not lose progress.
  • A save from an old game version opens in a new version (migration).
  • A save from a new version opens in an old version — the client must refuse cleanly, not guess at fields.

5. Account removal and iCloud / Google Play disabled

Scenarios:

  • The player removed their Apple ID or signed out of iCloud in iPhone settings. Game launches — what does it show? Usually — the local save without cloud. Worst case — the app crashes trying to access an unavailable CloudKit container.
  • The player disabled “iCloud Drive” for this specific app in Settings → iCloud.
  • The player deleted the game, reinstalled it, and isn’t signed in to an Apple ID. The save should be offered for recovery via an alternate login (Game Center, Facebook, custom).
  • On Android — the player revoked the Google Play Games permission for the game.

6. Restoring progress on a fresh install

The defining moment of player UX. They reinstall or change devices — the first launch should return their progress.

What to test:

  • Cold start: clean install → sign-in → cloud save pulled → progress restored.
  • If the cloud save is further than the tutorial progress of the new install — what do you merge? If the game already took the player through the tutorial and then pulled a save, you need to transition to the restored state correctly, not leave two parallel progressions.
  • Restore speed (downloading a 200 KB save on 3G should not take 30 seconds).
  • A loader and visible status — the player must not think their progress is gone.

7. Edge cases that bite

  • Offline → online merge: the player played offline for a week and accumulated progress. They come online — the client tries to sync. If there’s a fresher save in the cloud from another device, who wins?
  • Save version newer than client version: the player updated on iOS to a new save schema. The Android client (still on the old version) pulls that save — it must say “update the game”, not break.
  • Game downgrade (a test scenario — you shipped a bug, roll the version back). A new-version save must not lock the player out of the older version.
  • Device clock: the save timestamp is used for conflict resolution. The player sets the clock a year back — and can “win” against a fresher cloud save. Use a server timestamp whenever a backend is available.
  • Family account: iCloud Family kids can share purchases but not saves. Verify that a specific device’s save doesn’t end up shared across the family.

8. Tools for testing

  • Xcode → Devices and Simulators → Open Console — application logs from a real device, including CloudKit errors.
  • xcrun simctl — manage the simulator’s iCloud account.
  • Android Debug Bridge (adb shell pm clear <package>) — wipes local data. A convenient way to simulate a reinstall without reflashing the APK.
  • Proxy (Proxyman / Charles) — intercept requests to your own backend and inject responses (e.g. return “save corrupted” from the server to verify the error UI).
  • Network Link Conditioner (macOS / iOS) — emulate a bad network during sync.
  • Test Apple IDs and Google accounts — several on the team, to cover cross-account scenarios.

9. The compact checklist

The minimum before release:

  • Save succeeds on 2G/3G and across network drops.
  • A two-device conflict resolves under a thought-through strategy (document which one).
  • Cross-platform migration: progress — yes, IAP currency — via a server-side wallet, subscription — correct state.
  • A corrupt save recovers from backup, the app does not crash.
  • iCloud / Google account signed out — the game doesn’t crash, falls back to local save.
  • A reinstall returns progress in under 10 seconds on 4G.
  • A save from a newer version does not break an older client.
  • The device clock is not the sole source for conflict resolution.

Cloud save is the part of a game where failure costs the player hours of progress and the team a one-star review. Worth testing like you test payments.