Статус: принято Максом 2026-08-02. Иерархия уровней жизни задана им, разбор и границы — совместные. Решение и цитаты — журнал «The Levels Of Life Are Named».

Зачем этот документ. Боевой скоуп оброс миром, флоу и показом, и это перестало быть вопросом вкуса: у него нет границы боя, поэтому границу объявляют вручную, и каждый новый участник обязан на неё подписаться. Кто не подписался — ломается молча. Здесь записана целевая иерархия, список легаси на вырывание и порядок фаз, чтобы заход не пришлось изобретать заново.


1. Чем уровень отличается от состояния

Скоуп заводится, когда есть все четыре признака. Три — хватит объекта состояния, и скоуп будет церемонией.

  1. Граница — наблюдаемые в коде начало и конец.
  2. Состояние, обязанное умереть на границе — иначе течёт в следующий раз.
  3. Свои участники — сервисы, бессмысленные вне уровня.
  4. Своя композиция — набор участников зависит от того, каким уровень родился.

Четвёртый признак — тот, из-за которого заход и случился: роль в коопе меняет состав, а не поведение, и ветвление по ней в рантайме было ошибкой.

2. Целевая иерархия

Приложение (Root)              процесс
└─ Мир                          persist-сцены: арена, камера, показ, время
   └─ Сессия                    ЧЬЁ состояние играем: Owner | Guest
      └─ Занятие                Двор | Забег | Ристалище | PvP-матч | Дев-арена
         └─ Узел                только внутри Забега: бой | магазин | событие | привал
            └─ Бой              заказчиков четыре: узел, Ристалище, PvP, тест
УровеньГраницаЧто умирает на нейКомпозиция зависит от
Мирпроцессничего
Сессиявход в хаб → выход в главное менюRunState, сеть, право писать сейвOwner / Guest
Занятиевход в забег/ристалище/матч → выходкарта акта, счёт матча, скин аренывида занятия
Узелвход в клетку → результатпредложенное, купленное, счётчик ретраевтипа узла
Бойlaunch → outcomeлента, RNG боя, реестр юнитов, расстановкароли сессии

Бой НЕ дочерний Забегу. Заказчиков четыре, и один из них — тест: боевой скоуп обязан собираться без RunState, без сети и без карты. Это и есть проверка, что рез прошёл по правильному месту.

Двор, Ристалище и боевая арена — три скина одной персист-арены и взаимоисключающи по построению (ГД: Двор гильдии). Поэтому Двор — не «место между занятиями», а одно из Занятий, дефолтное.

3. Что решает каждый уровень

Мир

Арена (один экземпляр ArenaLayoutData), камера-риг, TimeScaleService, презентеры мира, ввод, держатель тел. Живёт весь процесс — как сейчас WorldLifetimeScope, только теперь без дублей снизу.

Держатель тел — решение Макса 02.08.2026 (журнал «The Simulation Belongs To The Battle»). CombatSimulation рождается и умирает вместе с боем; вне боя тела на арене (двор, Ристалище, ростер между забегами) держит отдельный мировой слой, а не замороженная симуляция.

Подъёмным это делает шов источника кадра: CombatPresenter рисует только из кадра — состав видов, снаряды, интерполяцию, события, — а живая симуляция нужна ему лишь для dev-оверлеев. Значит поставщиков может быть два, и показ не заметит разницы:

КогдаКто поставляет кадр
бойBattleTapePlayback — лента, с лагом показа
вне боястатичный кадр мира: тела стоят, позы не меняются

Отвергнуто при выборе: мировая симуляция с RNG боя за швом (дешевле на два дня, но граница боя остаётся объявляемой вручную) и мировая симуляция с пересевом при рождении скоупа (полдня, но «пересев вырван с корнем» тогда не выполняется честно).

Сессия — самый недооценённый уровень

Здесь решается чьё состояние мы играем, и отсюда растут две композиции:

OwnerGuest
RunStateгрузит из своего сейва, владеетполучает по сети
Автосейвпишетне существует вовсе
Гильдия и метасвоячужая, только на просмотр
Роль в боюсчитает и раздаётсмотрит ленту

У гостя нет своей гильдии — он играет в чужом сейве. Значит RunStateService, который сегодня один на всех и лежит в Root, у гостя не должен существовать: он пишет на диск. Сегодня не выстрелило только потому, что гость никуда ещё не заходил.

Роль сети живёт здесь и только здесь. В бою она не спрашивается — она выбирает инсталлер.

Занятие

Двор, Забег, Ристалище, PvP-матч, Дев-арена. Общее: конечное мероприятие со своим состоянием и выходом в хаб. Взаимоисключающи — включён ровно один.

Узел

Уже существует, но мимо DI: BattleFlow создаётся через new внутри GameFlowRootLifetimeScope это записано прямым текстом). Уровень есть, скоупа нет, зависимости прокинуты руками — тот же симптом, что у боя, в другой форме.

Бой

Лента, RNG боя, BattleTapePlayback, реестр юнитов, телеграфы, диагностика, расстановка, драйвер тика симуляции, кооп-половина по роли сессии.

4. Где PvP

Отдельный вид Занятия, не вариация Забега. У забега карта, мементо, экономика узлов, прогрессия Сосуда; PvP-матчу не нужно ничего из этого, зато нужно своё — счёт, раунды, приватность расстановки, общий гейт «оба готовы». Наследовать забег значило бы втащить пол-игры, чтобы половину выключить.

Следствие приятное: первая версия («расставились друг против друга, оба жмут готов») — это Занятие с одним Боем внутри, поверх готового Ристалища. Из хаба открывается соседней дверью с Ристалищем.

В ГДД механики PvP нет — в дизайн-доке он сегодня упомянут ровно один раз и вскользь. Когда дойдём до содержания матча (счёт, составы, что ставится на кон), это заводится через gdd-scribe/content-design, а не здесь: тут только его место в иерархии.

5. Рассмотрено и отвергнуто

Кандидат в уровниВердикт
Профильданные, не скоуп: своих сервисов нет, переключение идёт через меню с перезагрузкой. Станет скоупом, если появится смена профиля на лету
Гильдияу Owner совпадает с Сессией один в один. Нужен, только если дом можно сменить не выходя в меню — сейчас нельзя
Актвнутри Забега граница есть (карта, пул перезапусков), своих участников почти нет. Именован, не введён
Соединение«хост ушёл = конец сессии», реконнекта нет → соединение совпадает с Сессией
Игрок / раздача Сосудовсостояние, не уровень. Хост меняет распределение Сосудов в любой момент забега (ГД: Coop Overview) — скоуп на игрока сломался бы на первой перетасовке
Роль в боюне уровень: приходит из Сессии и лишь выбирает инсталлер

6. Легаси на вырывание

HARD: чинится корнем, а не заплаткой. Всё перечисленное существует потому, что уровня не было, и исчезает вместе с появлением уровня. Ни один пункт не переезжает «пока так».

ЧтоГдеПочему исчезнет само
Второй экземпляр ArenaLayoutDataCombatLifetimeScope.BuildArenaLayoutарена принадлежит Миру; комментарий в WorldLifetimeScope уже утверждает это, а код делал наоборот
Ручной пересев RNGBattleBootstrap.ReseedForBattleновый боевой скоуп — новый генератор с суб-сидом узла
_time.SetPaused(false) в трёх местахBattleBootstrapTimeScaleService.Dispose оживает вместе с границей боя
ResetBattle как «граница боя»CombatSimulation + подписчикиграница становится созданием и уничтожением скоупа
Reset() у ленты, приёмника, стримераBattleTape, TapeIntake, TapeStreamerто же: нечего сбрасывать у объекта, который родился с боем
Флаг роли и четыре ветвления по нейIBattleAuthority и его читателироль выбирает инсталлер, а не проверяется в Tick
Гейт Enabled у раздачиBattleTapeBroadcastостаётся только честный вопрос «есть ли слушатели», и он про пиров, а не про роль
BattleFlow через new мимо DIGameFlowузел получает свой скоуп
WorldStageController, BattleBootstrap в боевом скоупеCombatLifetimeScopeоба принадлежат Миру и Узлу соответственно
Очередь команд симуляцииEnqueueCommand, ICombatCommand, PauseCommand, ResumeCommandпользователей ноль (техдолг §3.21); сносится в фазе 1, вместе с оговоркой про счётчик тиков на паузе
Сброс приёмника у гостятехдолг §3.22заводился как заплатка к отсутствию границы; с границей не нужен

7. Фазы

Точки коммита — по фазам; красное дерево между фазами не оставляем.

ФазаЧтоКритерий приёмки
1а. Источник кадрашов «кто поставляет кадр показу»; мировой держатель тел; показ вне боя перестаёт зависеть от лентыарена вне боя показывает отряд, когда симуляции нет вовсе
1б. Боевой скоупсимуляция, системы, RNG боя, лента, расстановка рождаются с боем и умирают с нимбоевой скоуп поднимается в тесте без RunState, сети и карты
1в. Вырывание легаси§6 целиком: пересев, ручные сбросы, флаг роли, очередь командни одного ручного «объявления границы боя» не осталось; полный EditMode зелёный
2. СессияOwner / Guest; сюда переезжают RunStateService и сетьу гостя нет сервиса, пишущего сейв — проверяется тестом; роль коопа больше нигде не спрашивается в рантайме
3. ЗанятиеДвор, Забег, Ристалище, PvP-матч, Дев-аренавход в Ристалище и в дев-арену не проходит через петлю забега
4. Узелузел получает скоуп, BattleFlow уходит из newзависимости узла разрешаются контейнером

Мультиплеер продолжается после фазы 2: до неё роль в бою жить негде, а после — она уже готова.

8. Риски

  • Заход трогает сердце игры, а не край: боевой скоуп сегодня держит и мир, и флоу, и показ. Отсюда правило про точки коммита и про полный EditMode на каждой фазе, а не в конце.
  • Соблазн развести уровни глубже, чем нужно. Профиль, Гильдия и Акт намеренно не вводятся — §5 существует, чтобы через месяц никто не «дозавёл» их из общих соображений.
  • Пересоздание боевого скоупа во время боя (хост вышел) сносит показ посреди кадра. Поведение придётся определить явно: доигрывает накопленную ленту или замирает с сообщением. Сегодня это тоже не определено, но молча.