SashegDev e6a95a0f85
Build Delta / Build paperclip jar (push) Successful in 8m22s
feat(delta): let plugins send particles beyond the vanilla 32-block cut
Частицы не долетали дальше 32 блоков, при том что механика способностей бьёт
на 120. Игрок дальше 32 блоков не видел прицела вообще, но удар приходил.

Причина оказалась на стороне сервера, а не клиента. В ServerLevel есть
    if (blockPos.closerToCenterThan(pos, longDistance ? 512.0 : 32.0))
где longDistance это параметр alwaysShow. По умолчанию он false, то есть 32
блока. Но Bukkit-API этот флаг не открывает: в CraftWorld.spawnParticle он
захардкожен в false, а параметр force уходит в overrideLimiter. То есть ни
один плагин на Paper не мог отправить частицу дальше 32 блоков.

Само поле alwaysShow сериализуется в ClientboundLevelParticlesPacket, поэтому
клиент со своей стороны отсечку тоже снимает — тем же механизмом, что и
ванильная команда /particle в режиме display.

Что сделано:

1. World#spawnParticleLongDistance(...) — default-метод, а не абстрактный.
   Причина: абстрактный метод в интерфейсе World ломает бинарную совместимость
   с любой сторонней реализацией. Фолбэк здесь ведёт себя как обычный
   spawnParticle, то есть деградация — «слишком короткая дальность», а не
   «частиц нет».

2. CraftWorld переопределяет его. Дистанция применяется фильтрацией списка
   получателей ДО вызова ServerLevel, а не патчем самого ServerLevel:
   sendParticlesSource принимает готовый receivers, поэтому достаточно отсечь
   его самим. Ванильная проверка остаётся нетронутой. Это принципиально —
   меньше правок в ванильных патчах, меньше риска конфликтов.

3. Конфиг extended-particle-distance перестал быть мёртвым. Раньше он был
   объявлен в DeltaConfig и выводился в /delta, но нигде не читался при отправке
   частиц, поэтому переключатель не делал ничего. Теперь он работает как
   потолок: плагин запрашивает сколько нужно, сервер режет до 128.
   extendedParticleDistanceEnabled=false возвращает стоковые 32.

Замер: /delta теперь показывает "extended distance 128.0 blocks" вместо "off".
2026-10-05 17:30:57 +00:00
2024-12-12 10:58:58 +01:00
2025-06-17 15:45:25 +02:00
2025-04-12 17:27:00 +02:00
2025-04-12 17:27:00 +02:00
2024-12-12 10:58:58 +01:00

Delta

Экспериментальное серверное ядро на базе Paper для 1.21.8.

Сделано под узкие задачи: PvP-сервер с большим количеством кастомных способностей, тяжёлыми визуальными эффектами (Display-сущности, частицы, звуки) и ограниченным железом. Основная цель — выжать больше производительности с 4 vCPU / 8 ГБ RAM, не ломая совместимость плагинов.

Не для публичного использования. Ядро собирается под конкретный сервер и не имеет ни стабильности, ни гарантий безопасности. Не публикуйте его и не стройте на нём публичные проекты.


Совместимость — это приоритет

Delta — не форк с поломанным API. Плагины видят ровно то же, что и на Paper:

Что Значение Почему
Группа артефакта io.papermc.paper Versioning читает pom.properties по пути группы — смена ломает Bukkit.getBukkitVersion()
Версия 1.21.8-R0.1-SNAPSHOT плагины парсят эту строку под API-фичи
Brand-Id papermc:paper по нему работает ServerBuildInfo#isBrandCompatible
Пакеты io.papermc.paper.*, net.minecraft.*, Bukkit API не трогаем вообще
Порядок тиков стоковый многопоток не трогает семантику

Отличие только в отображении: Brand-Name = Delta, поэтому в F3 видно server: Delta. Это единственное пользовательское отличие.

Все новые классы лежат в me.sashegdev.delta. Методы Bukkit API, которых Paper не хватает, добавлены как default-методы — так ничего не ломается бинарно, и плагин, который на них ссылается, продолжает работать на стоковом Paper через fallback.

Что добавлено

/delta — инспекция и тюнинг

/delta status                      текущие значения конфигурации
/delta displayrange <блоки|0>      дальность трекинга Display, 0 = стоковая
/delta displaylead <тики|0>        предсказание позиции Display, 0 = стоковое
/delta clear                       сбросить Display-настройки к стоку
/delta help

Периметр delta.admin. Всё, что меняется через /delta, пишется в paper-global.yml и переживает рестарт. Paper не даёт публичного API для записи глобального конфига, поэтому добавлен saveGlobalSection(): он пишет только секцию delta и не трогает остальные узлы файла, так что сломать чужие настройки невозможно.

Бюджет Display-сущностей

Кламп дальности трекинга для Display живёт в org.spigotmc.TrackingRange#getEntityTrackingRange — это git-отслеживаемый исходник Paper, поэтому декомпилированный код Minecraft патчить не пришлось.

Display-сущности не имеют физики и коллизий, поэтому их отсечение по дальности безопасно: клиент просто их не видит, серверное состояние остаётся корректным. Это снимает основную нагрузку от способностей, спавнящих десятки BlockDisplay с viewRange=64.

Отключено по умолчанию:

delta:
  display-tracking-range-enabled: false
  display-tracking-range: 64
  particle-budget-per-player-per-tick: 0
  sound-budget-per-player-per-second: 0
  extended-particle-distance-enabled: false
  extended-particle-distance: 128.0
  decoupled-sound-range-enabled: false
  display:
    predictive-lead-ticks: 3
  sound:
    propagation-blocks-per-tick: 10.0
    decay-exponent: 4.0
    client-compensation: false
    min-audible-gain: 0.004

Звук: раздельные громкость и дальность

В ванилле слышимая дальность выводится из громкости (SoundEvent#getRange = 16 * volume). Единственный способ сделать звук слышимым за 150 блоков — volume=10, и тогда всё клиппит в упор. Delta разделывает эти параметры:

world.playSound(loc, sound, category, volume, pitch, range);
world.playSoundAttenuated(loc, sound, category, volume, pitch, range);

playSoundAttenuated отправляет ровно один пакет на игрока, отложенный до тика прихода волны. Кадрового обновления громкости нет вообще — иначе при десятке источников это N пакетов × M игроков каждый тик, то есть ровно тот трафик, который здесь и убирается.

Кривая нормализованная: gain = volume * (1 - d/range)^exponent. Ноль ровно на range, поэтому параметр дистанции осмыслен. Физический 1/r не годится: с порогом слышимости он срезал бы всё дальше ~1000 блоков и range просто не влиял бы на результат. Скорость волны — 10 блоков/тик, то есть 100 блоков за 0.5 с.

Плавность быстрых визуалов

DeltaDisplay держит политику предсказывающей телепортации. Display двигается пакетами позиции, и клиент интерполирует их у себя — на реальном пинге визуал отстаёт и дёргается. Лечится не частыми пакетами (это ровно тот трафик, который мы убираем), а сдвигом старта: визуал рождается сразу на N тиков вперёд по своей же траектории, и teleport_duration = N говорит клиенту растянуть телепорт на эти же N тиков. Клиент никогда не отстаёт, пакетов столько же.

Диапазон 0–10 тиков. teleport_duration — уже стоковый Bukkit API, поэтому Delta не добавляет плагинам новых методов: политика централизована, сдвиг позиции делает сам плагин по траектории каждого визуала.

Баннер при старте

При запуске печатается блок с версией, коммитом, веткой, списком Delta-фич и предупреждением о статусе ядра. Баннер выводится из статического инициализатора PaperBootstrap до парсинга paper-global.yml, поэтому строки описывают механизм, а фактические значения показывает /delta status — иначе баннер печатал бы дефолты как будто это текущая конфигурация.

Конфигурация сервера

Готовые тюнинг-конфиги под 4 vCPU лежат в zernmc-config/ — трекинг сущностей, активация, хопперы, чанковая система, редстоун. Это партиальные файлы: Paper мержит их с дефолтами.

Сборка

Требуется JDK 21. Две фазы, обязательно отдельными вызовами — при объединении падает с ошибкой неявной зависимости applyResourcePatches -> processResources:

./gradlew applyPatches
./gradlew createMojmapPaperclipJar

Артефакт: paper-server/build/libs/paper-paperclip-1.21.8-R0.1-SNAPSHOT-mojmap.jar

Имя delta в fill { project("delta") } влияет только на публикуемые в Maven артефакты, локальный jar всегда остаётся paper-*.

CI

Gitea Actions, воркфлоу .gitea/workflows/build.yml. Раннер живёт на отдельной машине с 4 vCPU — на слабом хосте сборка невозможна.

Джобы выполняются в образе delta-builder:jdk21 (JDK 21 + node + git identity). Node обязателен для actions/checkout, git identity — для applyFeaturePatches, который делает commit.

Воркфлоу делает обе фазы отдельными шагами: объединённый createMojmapPaperclipJar падает на неявной зависимости.

Планируется

  • force/alwaysShow в публичном API частиц (сейчас недоступны плагинам)
  • Вынос симуляции «безопасных» чанков в worker-треды Moonrise — с чек-листом, исключающим BlockEntity и плагинные блоки, чтобы не сломать совместимость
  • Калибровка client-compensation для затухающего звука: клиент сам гасит позиционный звук, поэтому без компенсации итог тише расчётного. Формула подгоняется по замеру, поэтому вынесена под отдельный тумблер

Лицензия

Код ядра наследует лицензию Paper (GPLv3). Все изменения в me.sashegdev.delta — тоже GPLv3.

S
Description
Tschhh....
Readme 52 MiB
Languages
Java 100%