Решили: экраны узла показывает один общий класс NodeStageScreens — у хозяина и у гостя
одинаково; роль решает только КТО ОБЪЯВЛЯЕТ шаг. Признак «запас реликвий полон» едет по проводу
полем NodeStageState.InventoryFull, а не считается каждой стороной самостоятельно.
Почему: до этого экраны узла публиковала петля акта, а она собирается только владельцу
(ActivityInstaller, _role == Owner) — из восьми экранов узла гость видел ровно один, витрину
награды, и ту вторым, отдельным путём внутри GuestNodeStage. Отвергли очевидную альтернативу
«дописать гостю недостающие семь экранов»: она оставляет две половины, которые обязаны совпадать, и
следующий экран разъедется ровно так же. Один потребитель совпадать не с кем.
Признак места вынесли в состояние по той же причине: он определяет ТЕКСТ голоса («взять» против
«взять взамен того-то»), а общее согласие сравнивает голоса как строки побайтово. Пока гость считал
его сам и всегда получал false, при полном запасе у хозяина голоса не сходились никогда: витрина
не закрывалась ни у кого, и забег вставал.
Сказано: «Где вообще хоть словов что по разному? Еще раз. Запиши как хард правило. пока не сказал что работает по разному у хоста и у клиента - значит ВСЕ ДОЛЖНО БЫТЬ ОДИНАКОВО, ЭТО РАВНЫЕ ИГРОКИ. У нас так ВЕЗДЕ, надеюсь?» (08.08.2026, сессия eab27015).
Грабли: сдвинутый байт сделал два теста формата зелёными по неверной причине. UnknownKind_IsRefused
и TruncatedOptions_AreRefused собирали пакет руками и после вставки InventoryFull стали отказывать
по длине пакета, а не по тому, что заявлено в имени. Тест, который собирает байты вручную, переживает
смену формата молча — это стоит помнить при следующей правке кодека.
Владелец правды: NodeStageScreens.cs, NodeStageState.cs, тесты NodeStageTests
(InventoryFull_TravelsWithTheShelf).