worker-threads: -1 means "auto", and auto on 4 cores resolves to ONE worker.
MoonriseCommon.adjustWorkerThreads halves availableProcessors and then, if the
result is <= 4, collapses it further - 4/2=2, and 2<=3 becomes 1. That single
thread generated the spawn region serially.
Measured on zern.cc (4 vCPU, 8 GB), same jar, same flags, cold world vs warm:
before after
cold, overworld 6770ms 2944ms
cold, nether 1368ms 888ms
cold, total 38.5s 24.9s
warm, overworld 1084ms 803ms
warm, total 16.4s 12.2s
gen-parallelism "default" already resolved to true at startup, so it is now
spelled out rather than left to string comparison at boot.
3 workers is deliberately NOT set here. The main thread plus Netty share the
same 4 cores, and going to 3 trades main-thread headroom for generation that
does not happen every tick. That needs a profile, not a guess.
Also recorded the corollary: player-max-concurrent-chunk-generates is a rate
limiter, not an accelerator. Speeding up generation without raising it just
makes the engine faster behind a throttle that was sized for the old speed.
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.