Архитектура игрового цикла: стейт-машина забега, события как флоу, точки автосейва,
структура сцен и мультиплеер-сессия (фиксированное лобби + реконнект).
Опирается на 5. Технологический стек и архитектура и 6. Боевая модель.
Макро-флоу детерминизма не требует (это последовательность решений, не покадровая переигровка) — поэтому async/await тут уместен и не угрожает гарантиям детерминизма боя.
2. Иерархия флоу (стек push/pop)
GameFlow (root, composition root + VContainer)
├── Boot → MainMenu → Lobby (мультиплеер-сетап)
├── RunSetup: выбор ГМ [мини-игра], раздача Сосудов, перки [мини-игра], сложность
│ └── 💾 АВТОСЕЙВ (старт забега)
└── Run
└── Act loop (карта генерится из сида ЦЕЛИКОМ на старте акта)
├── MapState — выбор следующего узла
└── ResolveNode → push IEventFlow по типу узла └── 💾 АВТОСЕЙВ (перед ивентом)
├── BattleFlow: Prep → Combat → Outcome
│ └── 💾 АВТОСЕЙВ (перед наградами)
├── ShopFlow / TrainingFlow / HealFlow / TournamentFlow / RiskFlow
└── MiniGameFlow
└── Босс (гильдия-финалист) в конце акта → следующий акт
Каждый флоу — async UniTask<TResult> Run(ctx), await-ит под-флоу. Узел карты пушит флоу; завершился → pop назад. Никаких ручных enum-стейтов на верхнем уровне.
3. Ивенты как полиморфные флоу (без центрального switch)
public interface IEventFlow{ UniTask<EventResult> Run(RunContext ctx); // Бой/Магазин/Лечение/Риск/Турнир/Мини-игра}
Узел карты = { тип, данные }; резолв = создать соответствующий IEventFlow и await Run. Новый тип ивента = новая реализация без правки центрального кода (≥6 реализаций — интерфейс «по делу»).
4. RunState (durable) vs RuntimeUnit (на бой)
RunState — переживает забег и сохраняется: сид, текущий акт, граф карты + позиция, ростер гильдии (Сосуды + инвентарь реликвий/предметов), золото/ресурсы, травмы, сложность, привязка игрок→Сосуды, ГМ, разблокированные AI-профили. Всё по строковым ID.
RuntimeUnit (6. Боевая модель) — собирается из RunState на каждый бой, после боя отбрасывается; травмы/изменения пишутся обратно в RunState.
RunState = сейв; RuntimeUnit = эфемерный боевой слой. Чёткое разделение убивает прошлую боль «рантайм протёк в данные».
await _save.Autosave(runState); // 1) старт забега 2) перед стартом ивента 3) перед экраном наград
Сейв = снапшот RunState с ID → три строчки в нужных местах.
6. Бой и ретраи (до 2 раз)
BattleFlow: Prep → Combat → Outcome. Поражение (попытка 1–2) → возврат в Prep (переназначить реликвии/позиции/AI) → перезапуск. 3-е поражение — финальное.
Точка восстановления = автосейв «перед ивентом».
Детерминизм vs ретрай: саб-сид боя = runSeed + индекс_боя + номер_попытки. Каждая попытка воспроизводима и реплеится, но не идентична предыдущей; основная агентность игрока — изменить стратегию в Prep.
7. Мини-игры — модульные, изолированные
public interface IMiniGame { UniTask<MiniGameResult> Run(MiniGameContext ctx); }
Каждая — флоу за этим интерфейсом, плагается в трёх контекстах (создание Сосуда, выбор ГМ, ивенты карты). Отдельная сборка Guildmaster.MiniGames, чтобы не запутывать ядро. Добровольные (перк-мини-игра) — можно пропустить, вернув дефолт.
8. Карта акта
Генерится целиком из сида на старте акта (структура как Slay the Spire — игрок видит весь граф и планирует путь). Хранится в RunState (граф + позиция). Генерация карты, наград, пула магазина, ивентов — через IRngService (см. 3. Рандомизация).
Аддитивные геймплейные сцены:MapScene, BattleScene — грузятся/выгружаются по флоу через NGO scene management (синхронно у всех).
«Глянуть карту/меню в бою» (как в StS) = read-only UI-оверлей, читающий RunState (всегда в памяти), а не переключение сцен. Дёшево и доступно из любого места.
Так память чистая (боевая сцена выгружается), а карта/меню доступны в бою через UI поверх.
await _net.WhenAllPlayersReady(); // все закончили Prep / все загрузили боевую сцену
(Чистая версия старых AllPlayersLoadedScene / PlayerTurnEndConfirmed.)
Состав фиксирован в лобби (по SteamID), но реконнект поддержан:
Хост резервирует слот ушедшего.
Сосуды ушедшего продолжают драться по своему AI-профилю — автобатлер делает дисконнект graceful (нет паралича, как было бы при прямом управлении).
Управление ГМ при выходе владельца — временный fallback на хоста/другого игрока.
Реконнект: хост стримит текущий RunState → игрок забирает слот.
Автопауза при дисконнекте (в бою): хост ставит тиковый цикл на паузу. Резолюция на выбор: подождать / продолжить без игрока (его Сосуды на AI) / выйти в меню. Опциональный авто-таймаут против грифа бесконечной паузой.
Пауза = окно синка реконнекта: во время паузы вернувшегося клиента подтягивают (снапшот RunState + сим-снапшот или переигровка лога команд от старта боя). Это закрывает mid-battle реконнект.
Возобновление — синхронный отсчёт «3…2…1»: хост назначает общий тик старта, все клиенты возобновляются одновременно (иначе рассинхрон).
Пауза боя стопит тиковый цикл, не макро-флоу (в макро «пауза» встроена — флоу ждёт на ready-gate).
11. Сборки (обновить карту при реализации)
Флоу оркестрирует Combat + Guild + UI + сцены → нужны новые верхнеуровневые модули:
Guildmaster.Game — оркестрация + composition root (VContainer-bootstrap), зависит от Core/Units/Combat/Guild, поднимает UI.