Решили: инициализация Steam и прокачка его колбэков живут в своём классе — SteamBootstrap (IStartable + ITickable в корне), AppId там же константой.

Почему это запись, а не тихая правка: класс появился не из потребности, а из ДЫРЫ. SteamClient.Init и RunCallbacks жили в FacepunchTransportBootstrap, который удалили 02.08 вместе с высокоуровневым netcode. Файл делал две работы, а удалялся за одну — и с тех пор Steam в проекте не поднимался вообще: ни Init, ни RunCallbacks, ни AppId нигде.

Чем это опасно именно здесь. Симптом неотличим от штатного внешнего отказа: SteamClient.IsValid возвращает false, лобби «просто не создаётся», кнопка приглашения гаснет — ровно так игра ведёт себя, когда Steam не запущен. То есть весь кооп-код читался бы как «написан и не работает по внешней причине», а искать это пришлось бы на живом тесте вдвоём, где стоимость минуты самая высокая. Нашлось за минуту грепом по SteamClient.Init — но только потому, что вопрос «а всё ли готово к тесту» был задан ДО теста.

Колбэки качаем сами (asyncCallbacks: false): фоновый поток Facepunch отдавал бы события лобби и приглашений вне главного потока Unity, а трогают они UI и сеанс. Цена — один вызов в кадр.

steam_appid.txt в корне репозитория — иначе из редактора Steam не понимает, под каким приложением мы ходим. Число публичное (оно на странице магазина), секрета в файле нет.

Урок общий, не про Steam: удаляя файл, чьё имя называет ОДНУ работу, проверь, не делает ли он вторую. Здесь имя говорило про транспорт, а половина содержимого была про жизненный цикл платформы.

Владелец правды: Net/Session/SteamBootstrap.cs, steam_appid.txt, строка про Steam в CLAUDE.md.