Решили: присутствие режется по сторонам на отправке у хоста, а «кто за какую сторону играет»
получает единственного владельца — состав сеанса (ISessionRoster), от которого теперь производен и
ILocalPlayer.Team.
Почему: отсечь чужой курсор на отрисовке было бы дешевле на один метод и бесполезно по сути —
байты уже лежали бы в памяти клиента противника, а курсор в фазе расстановки это и есть чужой строй.
Отвергнутая альтернатива по владельцу: положить сторону в мероприятие («сторон нет в хабе, значит
это его свойство»). Не выдержала сверки с кодом — шов «за какую команду играет этот клиент» уже
существовал (ILocalPlayer, читают шестеро), жил в корне и пережил бы любое мероприятие; а режим
выбирается на входе в игру и внутри сеанса не меняется. У мероприятия остался ровно один вопрос,
на который отвечает только оно: сколько сторон у площадки (ActivitySideAssignment).
Сказано: «Курсор надо видеть лишь у членов своей команды. Надо завести нормальный источник правды кто какой команды, если еще нет, и использовтаь его в текущем пвп и в курсорах» (03.08.2026, сессия b307a49b). Тем же ходом выбран вариант «курсоры в бою видны, но приглушены» — прежнее правило кооп-кластера «курсор гаснет в бою» отменено.
Грабли:
- Источника правды не было вовсе, а выглядело как будто был. «Своя команда» жила литеральным
нулём в четырёх местах
DeploymentController, и пока за игру садился один человек, ноль был верен по совпадению. В PvP вдвоём оба клиента считали бы своей одну и ту же сторону — то есть PvP был сломан ещё до курсоров. При этомILocalPlayerс точным докстрингом («в PvP у клиентов разные Team») существовал с самого начала и расстановкой не спрашивался. - Не всякий ноль в расстановке означает «я».
SideOf(team)— это половина арены, а не сторона смотрящего: игрок за команду 1 обязан расставляться в правой зоне и у себя, и у противника, иначе бойцы у двух клиентов стояли бы в разных местах одной арены. Заменять там ноль нельзя, и это записано прямо в коде — иначе следующий заход поменяет всё подряд. - Забывать чужой курсор по давности пакета нельзя. Соблазнительно и неверно: курсор в покое не шлёт пакетов вовсе (в этом весь смысл dirty-check), поэтому «давно молчит» значит «человек не двигает мышью». Ушедший и сменивший сторону забываются по составу сеанса, который приходит надёжным каналом ровно тогда, когда надо забыть.
- Второй перевод «экран → мир» появился бы молча. Расстановка считала его сама; присутствию нужен
тот же ответ. Свели к одному шву
IPointerWorld— разъехались бы они не сразу, а на первой правке глубины камеры, и выглядело бы это как «курсор чуть-чуть не там». - Мейн-цвет едет по сети местом в наборе, а не значением: владельцем цвета остаётся палитра
(токены
--gm-color-player-1..4), даже когда цвет прилетел из чужого клиента.
Владелец правды: Core/Players/ISessionRoster.cs, Game/Session/Net/HostPresence.cs,
Game/Activity/ActivitySideAssignment.cs, тесты PresenceSidesTests.