2 Commits

Author SHA1 Message Date
sasheg dev cda7198c76 Модель предметов по базе ремейка: узлы, деревья, знание
Протокол:
- ItemData читает таблицу items/items.csv (218 предметов ремейка) и отдает
  числа: урон, дальность, темп, прочность, сколько ресурса за удар.
- ItemType: поля rid, researchScrap, techTreeBench, techTreeScrap,
  techTreeParent, admin. Урон по частям тела делится как в ремейке: голова
  вдвое, руки и ноги четыре пятых. Число в исходнике главнее таблицы.
- ItemRegistry: 218 предметов с рецептами, ценой исследования и веткой
  дерева. Новые вещи: salvage-тесак, бронедвери, гаражи, взрывчатка,
  компоненты, еда, мусор, верстаки и печи.
- NodeType: узлы с запасом (дерево 500/750/1000, камень 750, металл 500,
  сера 200) и меткой: крестик на дереве до двух, горячая точка на руде до
  трех.
- TechTree: ветки верстаков с предками, 125/250/500 скрапа.
- Новые пакеты: Dig, BreakProgress, CraftStart, CraftUpdate, ResearchStart,
  ResearchUpdate, TechTreeUnlock, KnownRecipes, TechTreeState, ContainerOpen,
  ContainerTake, LearnBlueprint.

Сервер:
- Knowledge: что игрок знает - рецепты и открытые узлы деревьев.
- Crafter: проверка рецепта, уровня верстака и ингредиентов; время крафта
  по формуле плана.
- ResearchTable: предмет плюс скрап, десять секунд, предмет превращается в
  чертёж; чертёж изучается и открывает рецепт.
- ResourceNode: запас, метка, последний удар отдает остаток.
- JunkPile: две-четыре бочки (серая - скрап и мусор, синяя - компонент,
  красная - нефть и топливо) и ящик с едой; пустая мусорка исчезает через
  60-180 секунд.
- RoadNetwork: сетка дорог и кольцо, точки спавна мусорки; генератор кладет
  асфальт и ровняет дорогу по ширине.
- Проверка досягаемости вынесена из changeBlock, игрок получил benchLevel.
- Сессия отвечает на новые пакеты, при входе шлет известные рецепты и
  открытые узлы.

Тесты: протокол 24 + 10, сервер 74 (было 57). Форк собирается чисто.
2026-10-21 19:54:22 +03:00
sasheg dev 7fbb26092f Каркас протокола: кадрирование, буфер и рукопожатие
Это первый настоящий кусок сервера. Пока он ничего не умеет, кроме того что
умеет правильно: принимать байты от чужого человека и не дать ему ничего
сделать с нами.

Формат кадра — четыре байта магии RSX1, версия, id пакета, длина тела и
само тело. Магия стоит в начале, чтобы порт мог сразу ответить, а не ждать
объяснений.

Главное здесь — границы. Сервер живёт тем, что вынимает числа из сокетов
чужих людей, и разница между дешёвой проверкой и аллокацией — это разница
между «противник стоил нам пары тактов» и «противник стоил нам кучи».

- Длина тела проверяется до того, как под неё что-то зарезервировано.
  Единственное, на что полезна вредная длина, — заставить сервер выделить
  память.
- Varint ограничен пятью байтами для int и десятью для long. Без этого
  десять байт 0xFF превращают пятибайтовое целое в бесконечный цикл.
- Длина строки ограничена до того, как под неё читают байты.
- Декодер держит не больше одного максимального кадра плюс неполный
  заголовок.

Неполные данные и испорченные — это разные вещи, и раньше это не было
разделено: конверт, разрезанный сетью посреди varint, выглядел как
испорченный и рвал соединение. IncompleteException означает «остальное
уже в пути», ProtocolException — «потока больше не будет». TCP режет кадры
почти всегда, так что первое поведение было бы отказом от большинства
соединений.

Позиция упакована одним varint, но доли у осей не равные: три поля по 26 бит
это 78 бит, они не влезают в 64. X и Z получают по 26 бит с шагом 1/32
блока, Y — оставшиеся 12 бит целыми блоками. Со смещением на 2048 не
вышло бы: для диапазона 0..4095 нужен тринадцатый бит, и это ровно тот бит,
по которому значение читается обратно отрицательным.

Первый прогон тестов нашёл все три ошибки сразу, две из них — в упаковке
позиции. Проверено 17 сценариев, включая «по одному байту», пятьдесят
кадров за одно чтение, чужую магию и объявленное тело в два гигабайта.

Собирается и тестируется одним JDK, без Gradle и без сети, как и всё
остальное в проекте.
2026-10-19 01:10:26 +03:00