Решили: приглашение, принятое посреди своей игры, рвёт текущий забег штатным путём (IRunControl),
а цикл игры сам видит, что сессия стала гостевой, и уходит в чужую игру, не показывая главного меню.
Почему: «сначала вернись в меню, потом жми» выглядит как костыль, но возврат в меню — это и есть
честное описание того, что обязано произойти: свой сеанс закрыть, мир сбросить, забег отправить в
сейв. Спорным был только показ меню по дороге — и он не нужен: игрок уже сделал выбор в оверлее
Steam, а лишний кадр меню между «принял приглашение» и «я в чужой игре» читается как сбой. Признак
«мы идём в гости» не заводился отдельно: состояние сессии уже различает хоста (Hosting) и гостя
(Connecting/Connected).
Сказано: «Нужно чтобы „присоединиться“ корректно работало у игрока где бы он не находило. Мб перед этим проигрываются действия в духе „вернуться в главное меню“, а лишь потом попытке соединения. Но мб можно и без таких костылей.» (Макс, 04.08.2026, сессия fef16360).
Грабли, две.
Первая — в самом цикле игры: на входе в виток он закрывал любую поднятую кооп-сессию (_coop.Leave()).
То есть подключение, пришедшее из оверлея во время забега, цикл оборвал бы сам, вернувшись в меню, —
и выглядело бы это как «приглашение не работает». Ветку «мы уже идём к кому-то» пришлось поставить
ВЫШЕ этой строки.
Вторая — моя, в тесте: я написала, что хост не должен принимать гостя, умершего между «привет» и «добро пожаловать». Ожидание неверное — «привет» приходит раньше обрыва, и хост честно отвечает. Значение имеет не это, а то, что обрыв доходит следом: не дойди он, гость остался бы в составе призраком, ему слали бы курсоры, а гейт готовности ждал бы его согласия вечно — бой не начался бы уже никогда. Тест переписан на этот инвариант.
Владелец правды: Game/Services/GameFlow.cs (ветка «уже идём к кому-то»),
Game/Session/CoopJoinInterrupt.cs, Game/Session/CoopDisconnectPresenter.cs, тесты
HandshakeDropTests.