Решили: присутствие — склеенный пакет всех курсоров ненадёжным каналом, 128 Гц с dirty-check
(PresenceSender), а плавность строится на приёме кривой Эрмита по паре «точка + скорость»
(PresenceInterpolator).
Почему скорость едет в пакете. Не ради механики — ради интерполяции. Линейная между точками даёт угол на каждом изломе траектории, а курсор в коопе только из изломов и состоит. С касательными кривая совпадает на стыке не только положением, но и направлением, и угол исчезает. Это то же решение, что у Фидлера в snapshot interpolation, и оно объясняет цифру 128: частота отправки не покупает плавность, она покупает размер буфера, а через него — задержку.
Экстраполяция ограничена 100 мс. Потерянный пакет продолжается по последней скорости, но недолго: курсор, улетевший по инерции, читается как баг, а замерший — как «человек отошёл». Второе честнее, потому что за сто миллисекунд рука успевает передумать.
Инвариант «присутствие не трогает игру» держат сборки, а не дисциплина: Guildmaster.Net не
ссылается на Guildmaster.Guild, поэтому дороги из курсора в RunState физически нет. Как только от
жеста начнёт что-то зависеть (перехват вещи, кубик), он перестанет быть присутствием и станет командой
с номером и подтверждением — и тогда переезд заметит компилятор.
Грабли:
- Номер пакета —
ushortи идёт по кругу. Наивное сравнение «новее = больше» на 65536-м пакете сделало бы все следующие «старыми», и чужой курсор замер бы навсегда. Сравнивается разность, приведённая кshort. - Тест на Эрмита сначала смотрел в середину отрезка — и был неправ. При одинаковых касательных на обоих концах середина это точка перегиба: кривая ровно пересекает прямую, и проверка прошла бы даже для линейной интерполяции, то есть не проверяла бы ничего. Смотреть надо на четвертях, где видно, что кривая ушла в сторону скорости и вернулась.
- Пакет обязан влезать в MTU: фрагментированное ненадёжное сообщение теряется целиком, и «потерять всех разом» хуже, чем не отправить одного. Отсюда потолок числа курсоров в пакете.
Не сделано намеренно: отрисовка курсора на экране. Это UI-слой и сцена; модель, кодек, отправитель и интерполятор к ней готовы и от неё не зависят — курсорный слой рисуется поверх и разметки экранов не ждёт.
Владелец правды: Net/Presence/ целиком; инварианты — PresenceTests (курсор в покое не стоит ни
пакета, потолок частоты, скорость меряется между замерами, роундтрип склейки, отказ на чужой версии,
интерполяция уважает направление, инерция с потолком, опоздавший пакет не откатывает курсор,
переполнение номера).