Решили: сеанс владения состоянием рождается ВХОДОМ В РЕЖИМ, а не бутом. GameBootstrap поднимает только мир; сеанс открывает выбор в меню («Создать» → владелец, «Присоединиться» → гость). Верхний цикл закрывает сеанс перед каждым показом главного меню.

Почему изменили своё же решение того же дня: утром сеанс открывался на буте — ровно потому, что меню спрашивает «есть ли сейв», и держатель состояния был нужен раньше первого узла. Макс описал модель входа (две кнопки, три режима, галочка онлайн-лобби), и стало видно, что это подпорка: роль и решение про лобби известны в момент выбора режима, а не за минуту до него. Открывать сеанс раньше значит подставлять роль по умолчанию и надеяться, что её потом переоткроют.

Что развело узел: «где лежит забег» вынесено в RunSaves — знание об этом спрашивают ДВОЕ из разных жизней (меню вне сеанса и RunStateService внутри), и разойтись им нельзя. Меню теперь спрашивает диск и активную гильдию, а не объект в памяти.

Дев-разрезы заводят сеанс сами (RequireRun): разрез — это тот же вход в игру, только без выбора. Без этого флаги на буте и bones со сплеша упирались бы в «сеанса нет».

Тест поймал смену контракта раньше прогона: ActivityLifeCycleTest открывал мероприятие напрямую, а мероприятие рождается внутри сеанса. Пришлось привести тест к реальности — он входит тем же путём, что игрок. Ровно для этого он и писался сутки назад.

Осталось: само меню на две кнопки и три режима (UXML, локализация, галочка лобби) — отдельный UI-заход; гостевой сеанс — за кооп-вертикалью.

Владелец правды: Game/Services/GameFlow.RunGameAsync, Guild/RunSaves.cs, Game/Session/SessionHost.cs.