Решили: гость показывает экран итога боя и взводит 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.