Решили: гость показывает экран итога боя и взводит battle.continue сам — по паре «место плюс фаза» (ProvingGrounds + Interlude) из уже едущего ActivityState. Исход берётся из ленты (BattleEndedEvent), «В меню» у гостя означает выйти из чужой сессии.

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

Грабли: экран исхода к коопу был готов заранее — он закрывается признаком срабатывания гейта и сам рисует «(N/M)». Не хватало ровно того, кто его закажет на гостевой стороне; искать это по UI-коду бесполезно, видно только по владельцу ключа. MessagePipe без using MessagePipe требует IMessageHandler<T> и не берёт лямбду — ошибка выглядит как «лямбда не делегат». Найдено по дороге и НЕ чинено: у гостя _activityCts в GameFlow не взводится, поэтому «В главное меню» из системного меню у него не срабатывает. Поэтому кнопка экрана исхода уходит через ICoopSessionControl.Leave, а не через IRunControl.

Владелец правды: Game/Session/Net/GuestActivityFollower.cs (ApplyOutcome), Core/Net/ReadyKeys.cs.