Решили: раздачу боевой ленты развели на две стороны — TapeStreamer у хоста режет готовые тики на
чанки и шлёт их, TapeIntake у гостя складывает их в свою ленту и просит повторить недоехавшие по
номеру. Поверх транспорта появился конверт: первый байт сообщения объявляет канал (NetEnvelope,
NetChannel).
Почему: три решения внутри, каждое с отвергнутой альтернативой.
- Готовность подаёт вызывающий (
Pump(readyThroughTick)), а не стример спрашивает симуляцию. Альтернатива «стример знает проCombatSimulation» связала бы раздачу с боем и потребовала бы симуляции в каждом тесте; заодно правило «что игроку уже можно видеть» осталось бы в двух головах. - Хвост короче чанка уезжает только по
Flush. Если досылать неполный чанк на каждом кадре, конец боя раздробится на однотиковые посылки — а в них едет исход, самое важное событие ленты. - Потеря лечится повтором по номеру, а не непрерывной дельтой. Чанк самодостаточен (решение от кодека), поэтому повтор — это те же байты из кольца на 32 чанка, а переупорядочивание и дубли не значат ничего. Сплошная дельта через границы чанков дала бы на проценты меньше трафика ценой «потеряли один — сломались все следующие».
Грабли: ChaosTransport намеренно не теряет надёжные сообщения — их доставку обеспечивает
транспорт, и терять их значило бы моделировать не сеть, а сломанный транспорт. Случай «чанк не
доехал» бывает на разрыве соединения, и проверять его пришлось своим DroppingTransport внутри
теста. Полчаса ушло на попытку выкрутить это профилем chaos, которого там нет по замыслу.
Второе: номер чанка писатель тратит только когда действительно пишет — на пустом диапазоне
(лента чистилась, бой не начинался) Write выходит до инкремента. Не заметив этого, легко завести у
гостя вечную дыру, которую он будет просить повторить до конца боя.
Владелец правды: Net/NetEnvelope.cs (канал и конверт), Net/Tape/TapeStreamer.cs,
Net/Tape/TapeIntake.cs; тесты TapeDeliveryTests — в них же живёт обещание «у гостя тот же бой»
при переупорядочивании, дублях и потере.