Локальные сохранения и миграции¶
Дата: 9 октября 2026. Статус: обязательные требования к реализации. Реализована основа сохранений версии 2: один [Serializable] класс GameProgress, система ProgressSaveSystem и миграция 1 → 2. Поведенческие проверки выполняются пользователем вручную и пока не подтверждены.
Начальная схема версии 1 содержит saveVersion, revision, playerId, savedAtUtc и progress.currentLocationId. Версия 2 переносит currentLocationId на корень единого класса. Стабильные ID локаций — Location01 … Location20; переименование этих ID требует миграции. Отображаемые названия и порядок отметок карты ID не меняют. Последующие мета-поля необходимо добавлять новой версией схемы.
Реализация: Assets/Scripts/Flow/GameProgress.cs, ProgressSaveSystem.cs, интеграция в FlowSystem.cs. Профиль принадлежит экземпляру flow, без singleton. Полный профиль доступен через FlowSystem.Progress; после целой игровой операции вызывается SaveProgress(), результат записи необходимо проверять. Автоматическая запись — после успешной загрузки локации, при уходе приложения в фон и выходе. Объект профиля не пересоздаётся при смене локации. Сейчас единственные реализованные игровые данные — текущая локация; демонстрационные предметы интерфейса не сохраняются. Метаданные версии, ID игрока и revision принадлежат системе сохранения.
Сериализация использует уже установленный Unity Newtonsoft JSON. [JsonExtensionData] сохраняет неизвестные поля без отдельного сериализуемого класса. Данные отключённых фич и неизвестных ID не фильтруются. Новые игровые поля следует добавлять явно в GameProgress, с новой версией и миграцией, а не использовать словарь дополнительных данных как основную модель игры.
Реальные файлы: progress.json, progress.json.tmp, progress.bak.json, progress.pre-migration-v1.json. При уже существующей копии миграции новая получает уникальный суффикс. Повреждённый основной файл при восстановлении архивируется в progress.json.recovery-<id>, исправный backup не заменяется им. Запись использует flush, проверку полного JSON и File.Replace для существующего основного файла. Поддержку атомарной замены на целевых платформах необходимо проверить вручную; неподдерживаемая операция возвращает ошибку, без удаления основного файла.
Загрузка проверяет основной, временный и резервный кандидаты; выбирает наибольший корректный revision одного игрока. Будущая версия в любом кандидате или конфликт игроков блокирует загрузку. При наличии только повреждённых файлов/следов старого профиля чистый профиль не создаётся. Flow показывает ошибку и кнопку повтора. Неизвестная локация тоже блокирует запуск, без замены на первую. При ошибке записи после загрузки сцены игра остаётся на экране ошибки, повтор повторяет запись, а не загрузку сцены.
Примеры перехода и ручной чеклист: save-fixtures/README.md. Миграция детерминирована: переносит поле, сохраняет остальные данные, удаляет старый контейнер только если он пустой. ID и revision сохраняются на шаге миграции; фиксация мигрированного профиля увеличивает revision и обновляет UTC-время как обычная успешная запись.
Подтверждено пользователем: прогресс сохраняется локально, формат — JSON, структура имеет явную версию, для регулярных изменений заранее предусмотрены миграции. Этот документ определяет контракт хранения и изменения схемы. Конкретные имена полей и файлов ниже — проектное предложение; правила сохранности данных обязательны для реализации.
1. Область и владение¶
Локальный JSON — источник прогресса текущей версии игры. Вход в аккаунт, отсутствие сети, отказ от уведомлений и выключение фич не должны мешать чтению или сохранению. Аккаунт и область будущих облачных функций принадлежат Core Gameplay, но облачная запись, загрузка и синхронизация прогресса сейчас не входят в реализацию. Они требуют отдельного решения; наличие аккаунта не переключает источник прогресса автоматически.
Файл находится в постоянном каталоге данных приложения, для Unity — под Application.persistentDataPath. Он не хранится в сцене, prefab, ScriptableObject, временном кэше или каталоге конфигурации. Конфигурация баланса и фич — отдельные данные.
Предлагаемые файлы для одного профиля:
| Файл | Назначение |
|---|---|
progress.json |
Последнее успешно записанное сохранение |
progress.tmp |
Кандидат новой записи; незавершённый файл не считается сохранением |
progress.bak.json |
Предыдущая проверенная запись для восстановления |
progress.pre-migration-vN.json |
Проверенная копия исходного формата перед миграцией; не затирается обычным автосохранением |
Один явный владелец профиля сериализует операции записи. Параллельные записи одного профиля запрещены. Снимок состояния согласован: нельзя одновременно сериализовать списание ресурса и ещё не применённую награду.
2. Версия и структура JSON¶
Предлагается единая целочисленная версия схемы saveVersion, начиная с 1. Она относится ко всему документу. Версия приложения, версия баланса, версия конфигурации фич и версия эксперимента — другие значения; они не заменяют saveVersion.
Пример минимального контейнера, а не финальная схема всех игровых данных:
{
"saveVersion": 1,
"revision": 1,
"playerId": "local-player-0001",
"savedAtUtc": "2026-10-08T12:00:00Z",
"progress": {},
"settings": {},
"experimentAssignments": [],
"completedOperations": []
}
saveVersion— версия структуры и смысла данных, а не число записей.revisionмонотонно растёт при успешной записи; помогает выбрать последнюю корректную локальную запись одного профиля.playerIdстабилен после первого создания; миграция, переименование и вход не генерируют новый ID и не перераспределяют A/B-группы.savedAtUtcи окончания таймеров используют UTC. Часы не являются единственным критерием выбора записи.progressхранит постоянный игровой прогресс, включая выключенные фичи.settingsхранит локальные настройки; при отдельном файле настроек к нему применяются те же правила версии и миграций.experimentAssignmentsхранит назначения по ID/версии эксперимента, а не глобальную конфигурацию экспериментов.completedOperationsпредставляет учёт операций для предотвращения повторной награды/списания. Конкретная структура журнала и его ограничение по размеру определяются отдельно без потери защиты от дублей.
ID оружия, предмета, локации, квеста и узла стабильны и не зависят от позиции в массиве или отображаемого имени. Значения enum не сохраняются как случайный порядковый номер; нужен явный стабильный код. В JSON нет ссылок на runtime-объекты Unity. Числа имеют определённые диапазоны и не используют NaN/бесконечность; преобразования не зависят от культуры устройства.
Новая версия нужна при изменении сохраняемого поля, его типа, единиц, структуры, обязательности, значения по умолчанию или трактовки. Добавление необязательного поля тоже фиксируется новой версией с явным правилом заполнения. Чистое изменение цены в конфигурации не меняет схему сохранения; перерасчёт уже сохранённого прогресса требует отдельного решения и миграции.
3. Что сохраняется¶
Состав определяется профильными правилами игры:
- заработанные характеристики, открытые локации, отметки побед, источники добычи и состояние спавнеров;
- модели оружия, копии, уровни, накопленная мощность, ранги эволюции и сборка;
- предметы, копии, уровни, эволюции и назначения слотов;
- ингредиенты, открытия добычи и купленные узлы дерева;
- этапы/частичные действия квестов, найденные сюжетные предметы, состояние ручного получения;
- текущие задания Pass, счётчики, завершения и отдельно получение бесплатных/платных наград;
- локальная запись подтверждённых покупок и восстановимых незавершённых операций; проверка покупки сервисом остаётся отдельным требованием;
- настройки и уже назначенные группы экспериментов.
Выключение фичи не очищает её раздел и не пересоздаёт его по текущим настройкам. Пока она выключена, её данные проходят миграции вместе с остальными. Неизвестный текущему каталогу ID сохраняется как неактивная запись; отсутствие контента не означает разрешение удалить заработанное.
Спавнер сохраняет живой состав и реальное время окончания перезарядки; выгрузка не создаёт награду. Отсутствие игрока только сдвигает готовность таймера, без офлайн-убийств или добычи. Точные позиции/текущая прочность восстановленных врагов не добавляются без отдельного правила. Смерть и смена локации сохраняют прогресс и применяют подтверждённые правила сброса временных эффектов/перезарядок, а не общий сброс JSON.
Получение награды и отметка её получения — одна логическая операция. Списание уровней и повышение эволюции — одна операция. Нельзя записать только одну половину. Повтор после перезапуска определяется по состоянию/ID операции и не выдаёт результат второй раз.
4. Цепочка миграций¶
- Каждое изменение схемы получает новый номер и явный переход
N → N+1. - Загрузка версии 1 в версии 4 выполняет
1 → 2 → 3 → 4. Пропуск релизов игроком поддерживается. Прямой универсальный переход «старая → текущая» не заменяет историю миграций. - Для каждой поддерживаемой старой версии существует полная непрерывная цепочка. Отсутствующий переход — ошибка загрузки, а не повод создать чистый прогресс.
- Миграция выполняется на копии данных до запуска игровой логики, начисления наград, обработки таймеров и назначения новых групп A/B.
- Каждый шаг принимает только свою исходную версию, преобразует данные, проверяет результат и лишь затем выставляет следующую версию.
- Все шаги завершаются и проходят итоговую проверку до замены основного файла. Промежуточные версии не записываются поверх исходного сохранения.
- Миграция детерминирована: не зависит от сети, случайности, времени запуска, активной A/B-группы, включённых фич или текущих цен баланса. Для переименований и преобразований используются зафиксированные правила конкретного перехода.
- Повторная загрузка уже текущей версии не выполняет миграции. При аварии до фиксации цепочка повторяется с неизменного исходника, без дублей наград или списаний.
- Старые поддерживаемые миграции сохраняются в проекте. Их изменение требует воспроизведения на примерах всех затронутых версий; обычный способ дальнейшего изменения — добавить следующий переход.
Миграция меняет представление данных, а не проигрывает игровые события. Она не запускает убийства, покупку узлов, открытие квестов или выдачу наград через обычные обработчики.
5. Правила изменения полей¶
| Изменение | Обязательное правило перехода |
|---|---|
| Новое поле | Явный безопасный default для старого прогресса; уже заработанное не сбрасывается |
| Переименование | Перенос значения; определённый приоритет, если присутствуют и старое, и новое поля |
| Изменение типа/единиц | Проверяемое преобразование с определённым округлением и обработкой переполнения |
| Разделение/объединение | Правило распределения и устранения дублей; нельзя случайно удвоить копии, ресурсы или награды |
| Новый ID контента | Явная таблица старых/новых ID; отображаемое имя не используется как ключ |
| Удаление поля/контента | Данные архивируются или сохраняются неактивными; удаление/компенсация требует явного решения и описания |
| Изменение правил эволюции | Уровни, ранги и накопленная мощность сохраняются по зафиксированному контракту, без автоматической покупки по новому порогу |
Парсер и сериализатор не должны молча терять неизвестные поля. Нужно сохранить их как дополнительные данные либо отклонить несовместимый документ без перезаписи. Миграция явно указывает, какие поля она преобразует/архивирует. Пустое необязательное поле, отсутствующее поле и повреждённое обязательное поле различаются.
Сохранение без saveVersion не считается версией 1 автоматически. Поддержка конкретного старого формата вводится отдельным распознавателем и переходом на подтверждённых примерах; иначе файл сохраняется для восстановления и выдаётся ошибка.
Версия выше поддерживаемой текущей игрой не загружается для записи. Понижение версии не выполняется автоматически. Старый клиент не перезаписывает более новый JSON своими defaults; игроку предлагается обновить игру. Это правило применяется и к резервным кандидатам.
6. Безопасная запись и восстановление¶
Порядок записи:
- Создать согласованный снимок состояния; завершённые логические операции представлены целиком.
- Сериализовать в отдельный временный файл в том же локальном каталоге.
- Завершить запись/сброс буферов, прочитать и проверить кандидата: JSON, версия, ID профиля, revision и инварианты данных.
- Сохранить проверенный предыдущий основной файл как backup. Повреждённый основной файл не заменяет исправный backup.
- Заменить основной файл атомарной операцией, поддержанной целевой платформой. Нельзя предварительно удалять основной файл. Если подходящего API нет, нужна проверенная схема восстановления при каждом месте прерывания до принятия платформы.
- Только после успешной фиксации сообщить, что прогресс сохранён. При ошибке оставить предыдущую рабочую запись, удерживать несохранённое состояние и показывать возможность повторить.
Перед миграцией дополнительно сохраняется неизменная копия исходного документа. Ошибка любого шага оставляет её и основной файл доступными; частично мигрированное состояние не запускается в игре.
При старте проверить основной файл и кандидаты восстановления, не доверяя только времени изменения. Выбрать последнюю полностью корректную поддерживаемую запись этого профиля. Временный файл может быть кандидатом только после полной проверки. При наличии записи будущей версии нельзя молча откатиться на старый backup и записать поверх неё.
Новый профиль создаётся автоматически только при отсутствии всех следов существующего сохранения. Если файлы есть, но они повреждены или несовместимы, показать восстановление/ошибку и сохранить исходники. Создание чистого профиля в такой ситуации — отдельное явно подтверждённое действие пользователя, без удаления исходного файла.
Недостаток места, авария, закрытие приложения и неудачный flush/replace не считаются успешной записью. Точная частота автосохранений уточняется при реализации; постоянные награды и операции эволюции/покупки требуют фиксации, а не только сохранения при выходе.
7. Обязательные проверки миграций¶
Для каждого перехода хранятся небольшие входные JSON-примеры, ожидаемый результат и описание преобразования. До принятия изменения проверить:
- каждый поддерживаемый старый формат до текущего, включая пропуск нескольких релизов;
- новый профиль и профиль с большим прогрессом, выключенными фичами и неизвестными ID контента;
- отсутствие потерь уровней, рангов, ресурсов, заданий, прав и назначенных A/B-групп;
- повторную загрузку текущей версии: миграция не выполняется повторно;
- сбой между шагами и при записи: исходник/backup сохранён, повтор не дублирует операцию;
- обрыв JSON, неизвестную будущую версию, отсутствующую версию и отсутствующий переход;
- переименование, преобразование числа, конфликт старого/нового поля и неизвестные поля;
- сохранённую награду/эволюцию/покупку: результат и отметка обработки согласованы;
- закрытие в каждой точке записи, недостаток места и восстановление из backup на целевых платформах.
Изменение схемы без повышения версии, перехода, примеров и проверки сохранности не готово к выпуску. Для перехода 1 → 2 добавлены примеры, но ручные проверки сохранности и целевых платформ ещё не выполнены.