SashegDev 1a2f3bbde7
Build Delta / Build paperclip jar (push) Successful in 7m57s
feat(delta): void dimension at 2:1 coordinate scale, plus World#convertLocation
Мир пустоты как кастомное измерение датапаком и пересчёт координат между
мирами с разным масштабом.

Механизм масштаба — тот же, что у Незера: поле coordinate_scale в типе
измерения, а пересчёт при переходе равен делению масштаба источника на
масштаб назначения. Для пустоты нужно ровно 2.0, то есть один блок пустоты
на два блока оверворлда. Проверено на сервере: coord scale overworld=1.0
void=2.0.

Биом сделан своим (delta_void:void), а не переопределением ванильного
minecraft:the_void. Переопределение трогало бы данные Mojang и конфликтовало
с любыми другими датапаками. Отличия от ванильного — только цвета:
sky_color и fog_color в ноль, плюс выключенный туман и вода.

Что потребовалось по дороге, все три ошибки поймал сервер, а не я:

1. У типа измерения в 1.21.8 обязательное поле logical_height. Без него
   сервер отказывается стартовать: "No key logical_height".

2. Файл измерения не принимает дискриминатор "type": "minecraft:dimension".
   Кодек LevelStem читает поле type как ССЫЛКУ на тип измерения, и мой
   дискриминатор уходил в неё как имя типа. Правильно — "type" со значением
   delta_void:void.

3. getAllLevels() живёт на MinecraftServer, а не на DedicatedServer, который
   отдаёт CraftServer#getHandle(). И dimensionType() в Paper уже разворачивает
   Holder, так что .value() там не нужен и ломает компиляцию.

Отдельно: World#getCoordinateScale() в Paper УЖЕ есть, причём реализован
ровно так, как я собрался писать. Для чтения масштаба ядро менять не нужно.
А вот пересчёта координат в API нет ни в Paper, ни в Bukkit: Player#teleport
его не делает, потому что телепорт не портал. Добавлен default-метод
World#convertLocation с той же формулой, что и ванильная, и без изменения Y.

Хорошая новость про отказ сервера: на битый датапак он не стартует, а
падает в safeMode. Тихая загрузка сломанной генерации была бы хуже.

Диагностика частиц всё ещё в CraftWorld, снимается после проверки прицела.
2026-10-15 21:55:19 +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%