Добавлен -XX:MaxHeapFreeRatio=30 -XX:MinHeapFreeRatio=10 в start.sh на проде
и зафиксирован в README с замерами, потому что флаги JVM не переносятся
копированием конфигов — при переезде они теряются молча.
Замер, мир 18 МБ, 0 игроков, 100 с простоя:
коммит живые RSS
без флагов 630 МБ 278 МБ 0.99 ГБ
с флагами 389 МБ 274 МБ 0.69 ГБ
Минус 300 МБ RSS без единого ручного GC.
Причина была не в отсутствии Full GC: jcmd GC.run отрабатывает, живые падают
289 -> 248 МБ, но коммит остаётся на 725 МБ. G1 сжимает кучу только когда
свободно выше MaxHeapFreeRatio, а он по умолчанию 70. При 630 МБ коммита
свободно было 56% — ниже порога, и 350 МБ держались вечно.
Паузы Mixed (то, что ест кадр в рантайме) не ухудшились: median 11.0 -> 9.8,
p95 28.3 -> 28.1, max 38.1 -> 28.7 мс. Concurrent-циклов стало больше
(11 -> 18), общий p95 по всем паузам в одном прогоне вырос 126 -> 270 мс —
это замер без игроков, под нагрузкой флаг ещё предстоит перемерить.
DisableExplicitGC оставлен намеренно: он делает System.gc() no-op, и именно
поэтому освобождение сделано флагом, а не вызовом GC. README теперь
описывает профиль как 15-25 игроков с пиком 34, а не 30-40.
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.