Настоящая причина отсутствия server info: bf_write писал биты LSB-первым,
а Source читает/пишет MSB-первым (подтверждено дизассемблированием
bf_read::ReadUBitLongNoInline в engine_srv.so).
Из-за этого WriteOneBit(true) попадал в поле соседнего потока фрагментов,
а ID сообщений записывались перевёрнутыми — сервер не распознавал
net_SignonState и не выставлял m_bSendServerInfo.
- write_one_bit/write_ubit_long/read_one_bit/read_ubit_long: MSB-first;
- write_u32/write_short/read_long/read_short: побайтово little-endian
(как ReadLong в движке);
- reliable_state кодируется через WriteUBitLong(16);
- тесты bit_order_matches_source и reliable_block_layout_is_canonical.
Главная причина отсутствия server info: SUBCHANNEL_BITS был 4, из-за чего
сервер читал subchannel=1 и 0 фрагментов, молча отбрасывая reliable-поток
(net_SignonState не обрабатывался → m_bSendServerInfo не выставлялся).
Подтверждено pcap-разбором и дизасмом engine_srv.so (CNetChan::ProcessPacket:
lea 0x3(%esi) — ровно 3 бита; and $0x1 — reliable-флаг 0x01).
Проверка поля клиента (0x5af) в GMod-сервере: для удалённого клиента
сигнон-блок не отправляется, пока флаг (вероятно m_bFullyAuthenticated,
ставится при успешной асинхронной валидации тикета) не выставлен.
Симптомы совпадают: connect принимается, netchan работает, сообщения
доходят, но ServerInfo не приходит. Дальнейшие шаги — в docs/RECON.md §13.2.
- Регулируемые экспериментальные ручки: SOURCE_RS_SUBCHANNEL_BITS,
SOURCE_RS_REL_STATE, SOURCE_RS_HELLO_EVERY_MS, SOURCE_RS_PROBE_SIGNON,
SOURCE_RS_RETRY_DELAY_MS, SOURCE_RS_NO_TICKET_REFRESH.
- docs/RECON.md §13.1: разбор логики сигнона по утёкшему исходнику
(SendPendingServerInfo/SetSignOnState/ConnectClient), формат challenge-ответа,
наблюдения по лимитам Steam (DuplicateRequest ~2-3 мин), что уже проверено.
- Сигнон-блок от сервера пока не приходит (наши пакеты доходят, connect
принимается) — следующая задача; все зацепки в доке.
- Современный Steamworks требует GetAuthSessionTicket с
SteamNetworkingIdentity = SteamID игрового сервера; generic-тикет сервер
отвергает синхронно (#GameUI_ServerRejectSteam). Тикет привязывается к
серверу из ответа challenge ('A': keysize u16, GSID u64, secure u8).
- Кэш тикета по SteamID сервера (как CSteam3Client::GetAuthSessionTicket),
ретраи connect с задержкой (10 с): повторный BeginAuthSession для того же
SteamID сервер считает дубликатом и отклоняет.
- Тикет запрашивается в момент connect (GSID уже известен), стартовый тикет
больше не нужен.
- docs/RECON.md §13: полный формат challenge/connect/reject, порядок состояний
сигнона, наблюдения по лимитам Steam.
- Вода: детект Water-шейдера (/), отдельный полупрозрачный
проход с френелем; поверхность/изнанка по нормали. Белая дыра на месте воды
(2D-скайбокс «вниз») устранена.
- 3D-скайбокс: выбор граней по area листа sky_camera (движок: area bits), а не по
AABB. Disp-грани не попадают в leaf_faces — выбор по листу центра грани; теперь
в скайбоксе есть большая плоскость flatgrass_2 (пропадала — торчала «стена»
бетонного пола). Защита >30% граней = скайбокс не выделяем (gm_flatgrass).
- Порядок: 2D-скайбокс фоном -> 3D-меш (depth Always, без записи) -> мир ->
вода. Скайбокс больше не проступает сквозь дальние стены карты.
- Лайтмапы: блок грани (size+1)^2 с индексом v*width+u; для displacement'ов —
плотная сетка по UV поверхности (CalcDispSurfCoords), а не проекция мира:
убрана «шахматка» теней на рельефе. Скайбокс-меш тоже сэмплит лайтмапы.
- Тесты: gm_flatgrass-диспы живут в скайбоксе (z≈-16000), пороги обновлены.
- docs/RECON.md §12: форматы листьев/граней, лайтмапы, скайбокс, вода.