Решили: высокоуровневый netcode из проекта удалён целиком. Работаем со Steam Networking Sockets напрямую: SteamNetTransport реализует наш INetTransport на relay-сокете Facepunch, рукопожатие и номера пиров переехали на наш уровень (CoopHandshake), сессия живёт на Steam-лобби. Из проекта ушли NgoTransport, FacepunchTransportBootstrap, запаркованный SimSyncProbe, объект [Net] из CoreScene и четыре пакета: netcode.gameobjects, multiplayer.tools, multiplayer.center, multiplayer.playmode.

Сказано: «Ох, ты ведь обожаешь все уничтожать? хихи. Так вот… РЕЖЬ и давай самый чистый способ напрямую со Steam! хихи» (02.08.2026, сессия c3712a93). Ему же принадлежит рамка, из которой это следует: «Планирую только Steam».

Почему: HLAPI продаёт репликацию сетевых объектов, RPC и спавн. У нас нет ни одного сетевого объекта и не планируется — бой раздаётся лентой чанками, состояние забега идёт логом команд, присутствие своим пакетом. NGO выполнял ровно две работы: переносил байты и одобрял подключение, и обе мы уже продублировали (конверт с каналом, отпечаток контента). То есть платили версионной зависимостью за написанное.

Отдельно проверено, и это добило вопрос: готовый com.community.netcode.transport.facepunch из Unity MCC объявляет зависимость netcode.gameobjects 1.0.0-pre.4 при нашей 2.11.2, репозиторий community-ный без гарантий совместимости, а внутри тянет Facepunch 2.3.2 от 2020 года. Тянуть его под нашу версию значило бы чинить чужой пакет ради того, что пишется своими тремя сотнями строк.

Что Steam даёт вместо этого: CreateRelaySocket у хоста и ConnectRelay(SteamId) у гостя идут через Steam Datagram Relay — IP не раскрывается, NAT проходится, трафик едет по бэкбону Valve. Для P2P это доступно любому партнёру бесплатно; договариваться с Valve нужно только про SDR для выделенных серверов, которых у нас нет.

Грабли: я советовала оставить NGO «как отладочный путь» и была неправа — это была защита написанного часом раньше, а не аргумент. Поймал Макс, процитировав мою же предыдущую формулировку. Реального выигрыша не было: логику отлаживают LoopbackTransport и ChaosTransport (два узла в одном процессе, потери по сиду), а единственное, чего они не дают, — настоящий сокет, и он теперь ровно один, стимовский, и проверяется на двух машинах, потому что один Steam-клиент = один аккаунт.

Вторая: рукопожатие пришлось перенести на наш уровень до реза, а не после. Оно жило в ConnectionApproval NGO, и вместе с пакетом ушла бы проверка версии и контента — а её отсутствие всплывает не отказом, а сломанным показом боя у гостя.

Владелец правды: Net/Transport/SteamNetTransport.cs, Net/Session/CoopHandshake.cs, Net/Session/CoopSession.cs, Net/Session/SteamLobbyService.cs. Границу стека держит тест NoHighLevelNetcodeTests: код проекта, манифест пакетов и сцены не должны содержать чужого сетевого стека — вернуть его можно было бы одним using в чужой правке.