Cloud Save в мобильной игре — чек-лист тестирования
Игрок прошёл первые 30 уровней на работе с iPad. Вечером запускает игру на iPhone — прогресса нет. Удаляет, переустанавливает — нет. Пишет в support, ставит одну звезду в сторе. Это худший пользовательский опыт в мобильной игре, и почти всегда виноват непротестированный cloud save.
Cloud save звучит просто — сохранили JSON в облако, достали на другом девайсе. На практике это 5+ систем, которые ломаются по-разному: платформенные API ведут себя не так, как в документации; конфликты разрешаются неочевидно; миграция между платформами почти всегда теряет покупки; save-файл может прийти повреждённым, а игрок — без бэкапа.
Этот гайд — что именно тестировать в cloud save мобильной игры.
1. Платформенные API: три подхода
iCloud (Apple). Два варианта:
NSUbiquitousKeyValueStore— лимит 1 МБ суммарно на приложение. Подходит только для очень компактных save’ов или метаданных (последний пройденный уровень, монеты). См. документацию Apple.CloudKit— полноценная база, до 10 ГБ в free tier пользователя. Поддерживает разрешение конфликтов черезCKRecordи change tokens. Для нормального save в игре — только этот вариант. См. CloudKit docs.
Google Play Games Services — Saved Games. До 3 МБ на snapshot, до 4 snapshot’ов на игру (можно использовать как backup slots). Конфликты — на стороне разработчика через SnapshotConflictResolutionPolicy. См. Saved Games guide.
Свой backend (Firebase / собственный API). Полный контроль, но больше сценариев теста. Самый частый выбор для cross-platform игр.
Что тестировать:
- Save проходит при медленной сети (>2 сек latency) и реконнекте.
- API-лимиты по размеру: что происходит, если save вырос до 4 МБ в Google Play Games — silent fail или ошибка?
- Rate limits — Apple и Google не любят, когда пишешь в облако каждые 5 секунд.
- Поведение когда iCloud или Google-аккаунт не залогинен или отключён в системе.
2. Конфликт между девайсами
Самый частый баг — игрок играет одновременно на двух девайсах (iPad на зарядке + iPhone в дороге), оба пишут в cloud. Что мержим?
Стратегии разрешения:
- Last-write-wins — просто, но теряет прогресс одного из девайсов. Подходит только для игр с линейным прогрессом.
- Highest-progress-wins — сравниваем по уровню, счёту, timestamp последнего события. Игрок не теряет прогресс, но может потерять покупки из «откатанной» сессии.
- Merge logic — объединяем по полям (валюты — max, открытые уровни — union, инвентарь — union). Сложнее, но безопаснее.
Тест-кейсы:
- Девайс A прошёл уровень 10, девайс B параллельно купил предмет. Запускаем поочерёдно — игрок не должен потерять ни уровень, ни покупку.
- Девайс A в офлайне 3 дня, накопил прогресс. Девайс B всё это время играл в онлайн. Девайс A выходит в сеть — что побеждает?
- Одновременный logout/login на двух девайсах в течение 1 секунды.
- Conflict-resolution UI: показываем игроку выбор «оставить версию A / оставить версию B» или решаем сами тихо? Если показываем — UI должен быть понятен игроку, не быть дампом JSON.
3. Cross-platform миграция iOS ↔ Android
Самая болезненная область. Игрок купил премиум-валюту в iOS-версии, переехал на Android — где деньги?
Что переносится через cloud save (вне зависимости от стора):
- Прогресс уровней, открытые контент, кастомизации, нагембейпленные ресурсы.
- Игровая валюта, заработанная игроком (не купленная за реальные деньги).
Что НЕ переносится автоматически:
- Купленная за реальные деньги валюта — IAP привязан к Apple ID и Google account. Если игрок купил на iOS и перешёл на Android, у Google нет записи о покупке. Решение: серверный кошелёк (валюта живёт на backend’е, IAP receipt верифицируется и пополняет cloud-кошелёк, а не локальный).
- Активные подписки — даже технически нельзя переносить из-за политик сторов.
Тест-кейсы:
- iOS → Android: создать прогресс + IAP на iOS, залогиниться той же учёткой (Apple ID, Google Sign-In, Facebook, custom account) на Android. Проверить: прогресс есть, IAP-валюта есть (если используется серверный кошелёк), активная подписка корректно отображается как «куплена на другой платформе».
- Android → iOS — аналогично.
- Игрок без linked-аккаунта (играет «гостем») переходит на новый девайс — прогресс должен предложиться к восстановлению через привязку аккаунта.
4. Save corruption и backup slots
Save может прийти битым: упала сеть в середине записи, кончилось место на диске, OOM при сериализации, баг в новой версии клиента, который пишет невалидный JSON. Что дальше?
Защита:
- Чек-сумма (SHA-256 или хотя бы CRC32) в начале save’а — при чтении сверяем.
- Atomic writes — сначала пишем в
save.tmp, потом переименовываем вsave.json. Прерванная запись не оставит битый файл. - Несколько слотов (Google Play Games даёт 4 snapshot’а — используйте как rolling backup).
- Версионирование — каждый save содержит
schema_version, чтобы новый клиент знал, как читать старый формат.
Тест-кейсы:
- Принудительно скормить игре битый JSON (через подмену файла на устройстве) — приложение не должно крашиться, должно восстановиться из бэкапа.
- Прервать запись save’а в середине (kill процесса, выключение питания) — следующий запуск не должен потерять прогресс.
- Save из старой версии игры открывается в новой версии (migration).
- Save из новой версии открывается в старой — клиент должен корректно отказать, а не интерпретировать поля наугад.
5. Удаление аккаунта и отключение iCloud / Google Play
Сценарии:
- Игрок удалил Apple ID или вышел из iCloud в настройках iPhone. Игра запускается — что показывает? Обычно — локальный save без облака. Худший вариант — приложение крашится при попытке доступа к недоступному CloudKit-контейнеру.
- Игрок отключил «iCloud Drive» для конкретного приложения в Settings → iCloud.
- Игрок удалил игру, переустановил, не залогинен в Apple ID. Save должен предложиться к восстановлению через альтернативный login (Game Center, Facebook, custom).
- На Android — игрок отозвал permission Google Play Games для игры.
6. Восстановление прогресса на новой установке
Главный момент пользовательского опыта. Игрок переустанавливает игру или меняет девайс — первый запуск должен вернуть прогресс.
Что тестировать:
- Cold start: чистая установка → логин → cloud save подтянулся → прогресс восстановлен.
- Если cloud save больше, чем тутор-прогресс новой установки — что мержим? Если игра уже включила игрока в туториал, а потом подтянулся save, нужно корректно перейти к восстановленному состоянию, а не оставить два прогресса.
- Скорость восстановления (download save 200 КБ на 3G не должен занимать 30 секунд).
- Loader и индикация — игрок не должен думать, что прогресс пропал.
7. Edge cases, которые ловят боль
- Offline → online merge: игрок неделю играл оффлайн, накопил прогресс. Вышел в сеть — клиент пытается синхронизировать. Если в cloud есть более свежий save с другого девайса, что побеждает?
- Версия save новее версии клиента: игрок обновился на iOS, попал в новую схему save’а. На Android’е старая версия клиента подтягивает этот save — нужно показать «обнови игру», а не ломаться.
- Downgrade игры (тестовая ситуация — выкатили багу, откатываем версию). Save новой версии не должен заблокировать игрока в старой.
- Часы на устройстве: timestamp save’а используется для разрешения конфликта. Игрок переводит часы на год назад — может «победить» более свежий cloud save. Используйте server timestamp при наличии backend’а.
- Семейный аккаунт: дети iCloud Family могут шарить покупки, но не save’ы. Проверить, что save конкретного устройства не уезжает в семейный шаринг.
8. Инструменты для тестирования
- Xcode → Devices and Simulators → Open Console — логи приложения с реального девайса, видно CloudKit-ошибки.
xcrun simctl— управление iCloud-аккаунтом симулятора.- Android Debug Bridge (
adb shell pm clear <package>) — чистит локальные данные. Удобный способ симулировать переустановку без перезалива APK. - Прокси (Proxyman / Charles) — перехват запросов к собственному backend’у, подмена ответов (например, вернуть «save corrupted» от сервера для проверки error UI).
- Network Link Conditioner (macOS / iOS) — эмуляция плохой сети при синхронизации.
- Тестовые Apple ID и Google-аккаунты — несколько штук в команде, чтобы покрыть кросс-аккаунт сценарии.
9. Компактный чек-лист
Базовый минимум перед релизом:
- Save проходит на 2G/3G и при дропе сети.
- Конфликт двух девайсов разрешается по продуманной стратегии (документировать какой).
- Cross-platform миграция: прогресс — да, IAP-валюта — через серверный кошелёк, подписка — корректное состояние.
- Битый save восстанавливается из backup, приложение не крашится.
- iCloud / Google аккаунт не залогинен — игра не падает, fallback на локальный save.
- Переустановка возвращает прогресс за <10 секунд на 4G.
- Save из новой версии не ломает старого клиента.
- Часы устройства не используются как единственный источник для conflict resolution.
Cloud save — это та часть игры, где провал стоит игроку часов прогресса, а команде — однозвёздочного review в сторе. Стоит тестировать как платежи.