Решили: экраны узла показывает один общий класс NodeStageScreens — у хозяина и у гостя одинаково; роль решает только КТО ОБЪЯВЛЯЕТ шаг. Признак «запас реликвий полон» едет по проводу полем NodeStageState.InventoryFull, а не считается каждой стороной самостоятельно.

Почему: до этого экраны узла публиковала петля акта, а она собирается только владельцу (ActivityInstaller, _role == Owner) — из восьми экранов узла гость видел ровно один, витрину награды, и ту вторым, отдельным путём внутри GuestNodeStage. Отвергли очевидную альтернативу «дописать гостю недостающие семь экранов»: она оставляет две половины, которые обязаны совпадать, и следующий экран разъедется ровно так же. Один потребитель совпадать не с кем.

Признак места вынесли в состояние по той же причине: он определяет ТЕКСТ голоса («взять» против «взять взамен того-то»), а общее согласие сравнивает голоса как строки побайтово. Пока гость считал его сам и всегда получал false, при полном запасе у хозяина голоса не сходились никогда: витрина не закрывалась ни у кого, и забег вставал.

Сказано: «Где вообще хоть словов что по разному? Еще раз. Запиши как хард правило. пока не сказал что работает по разному у хоста и у клиента - значит ВСЕ ДОЛЖНО БЫТЬ ОДИНАКОВО, ЭТО РАВНЫЕ ИГРОКИ. У нас так ВЕЗДЕ, надеюсь?» (08.08.2026, сессия eab27015).

Грабли: сдвинутый байт сделал два теста формата зелёными по неверной причине. UnknownKind_IsRefused и TruncatedOptions_AreRefused собирали пакет руками и после вставки InventoryFull стали отказывать по длине пакета, а не по тому, что заявлено в имени. Тест, который собирает байты вручную, переживает смену формата молча — это стоит помнить при следующей правке кодека.

Владелец правды: NodeStageScreens.cs, NodeStageState.cs, тесты NodeStageTests (InventoryFull_TravelsWithTheShelf).