fix(lint,command,scheduler): lpk alias, ReturnCount/IllegalCatch triage, scheduler facade

- command: replace /lp alias with /lpk (LuckPerms conflict, p.144);
  parkour alias kept; docs/changelog/javadoc updated
- lint p.169: restructure last 69 void early-returns into if-blocks and
  helpers; ReturnCount is now an error gate (0 violations)
- lint p.170: Throwable->RuntimeException on 3 boundaries; narrow 5
  catches to concrete types; document 14 intentional boundary catches
- scheduler p.21: add runTimerAsync to the facade (Bukkit + Folia
  backends); MetricsCacheStore and duels go through the facade, no
  BukkitRunnable/Bukkit.getScheduler outside scheduler/ (archunit
  freeze-store emptied); drop the unverified Folia badge from README
- tracker: mark p.26-29/32-33 done (fixed in fea15e6, verified)
This commit is contained in:
loki5512344 2026-10-10 14:52:10 +02:00
parent 8ff02cff18
commit b6712391fd
Signed by: boba
GPG key ID: 253067914055423B
79 changed files with 696 additions and 800 deletions

52
TODO.md
View file

@ -46,19 +46,32 @@
### Функциональность, которая не работает
- **21. Folia-поддержка — фикция.** Нет `folia-plugin.yml` (Folia не загрузит плагин). `MetricsCollector` использует удалённое API `BukkitRunnable.runTaskTimerAsynchronously` (MetricsCollector.java:73). `SessionStateManager.runTimer` → GlobalRegionScheduler, но тик мутирует блоки/entities (SessionStateManager.java:76, LifecycleTickManager.java:75, BlockPlacer.java:158, teleport в ParkourUser.java:93-95). → Либо выпилить бейдж Folia из README, либо: добавить folia-plugin.yml, перевести тик на runTimerAtLocation/runAtEntity, заменить BukkitRunnable.
- **21. Folia-поддержка — фикция.** ✅ **ЧАСТИЧНО (2026-10-10):** все вызовы планировщика идут через фасад `scheduler/`
(добавлен `runTimerAsync`: Bukkit → `runTaskTimerAsynchronously`, Folia → `AsyncScheduler.runAtFixedRate` рефлексией);
`MetricsCacheStore` переведён с `BukkitRunnable` на фасад, прямые `Bukkit.getScheduler()` из `DuelsCountdown`/`DuelsGenerator`
убраны — вне `scheduler/` их больше нет. Бейдж Folia из README убран (поддержка не проверена на Folia-сервере).
❌ Осталось: тик идёт через global scheduler, но мутирует блоки/entities (`SessionStateManager`, `LifecycleTickManager`,
`BlockPlacer`, teleport) → нужен region/entity-tick + `folia-plugin.yml`; без Folia-стенда не верифицируется (см. п.143).
- **22. Ghost-система — 100% мёртвый код.** `GhostManager`/`GhostRecorder`/`GhostPlayer` нигде не инстанцируются (grep: только само-ссылки). Конфиг `ghost-mode.*` не потребляется. → Подключить к lifecycle (запись в saveGhost/спавн в spawnGhosts) ИЛИ удалить подсистему и конфиг.
- **23. Coop-режим нерабочий.** Ни одно `Session.create` не строит `CoopGenerator` (CoopMode.java:134). `/parkour join <игрок>` ведёт в спектаторы. → Реализовать создание coop-сессии (лобби/инвайты) или выпилить режим из меню и регистрации.
- **24. Coop: латентный StackOverflow.** `ParkourPlayer.unregister()` → `CoopMode.leave()` → `ParkourUser.leave()` → `UserRegistry.unregister()` → `user.unregister()` — бесконечная рекурсия (ParkourPlayer.java:102-107, CoopMode.java:115-121, UserRegistry.java:58-67). Нет guard-флага. → Добавить in-flight guard в unregister (например, reentrancy flag в ParkourUser).
- **25. Лидерборд: тайминги сломаны.** `Score.getTimeMillis()` ждёт ровно 3 части через `:` (Score.java:36-55), но генератор пишет `mm:ss.SSS` (2 части) и `HH:mm:ss.SSS` (третья `02.123` → NFE). Итог: TIME-сортировка и тай-брейк мёртвые, Race-лидерборд не обновляется. Плюс переполнение `int` в m*60*1000+... (Score.java:51). → Унифицировать формат времени; парсить `mm:ss.SSS` и `HH:mm:ss.SSS`; перейти на `long`.
- **26. ConfigUpdater уничтожает пользовательские данные.** `merge()` перегенерирует весь файл из шаблона при каждом старте (ConfigUpdater.java:85-127): теряются листы (`options.leads.amount`, стили), кастомные ключи, комментарии; алгоритм отступов (`prevIndent -= 2`) ошибочен; `ignoredSections` не защищают. → Точечный merge только отсутствующих ключей, сохранять листы/комментарии, либо заменить на библиотеку (e.g. `me.mattstudios` / SimpleConfig) или `Bukkit's config addDefaults`.
- **27. SQL: утечка Hikari-пулов.** `StorageSQL.init(mode)` вызывается на каждый режим (6 лидербордов), каждый раз пересоздаёт `connectionManager` (StorageSQL.java:66-77, LeaderboardStorage.java:35) → до ~10 соединений висят навсегда. → Один пул на плагин, инициализация один раз.
- **28. SQL: блокирующий connect на main thread** (SQLConnectionManager.java:34-56) — при лежащей БД включение висит до 5с×режимы. → `setInitializationFailTimeout(-1)` + асинхронный connect.
- **29. SQL: адаптив не стартует.** `AdaptiveServices.init` вызывается до создания лидербордов, а `StorageSQL.runWhenConnected` выбрасывает колбэк при не-подключении (StorageSQL.java:45-51). → Очередь колбэков в SQLConnectionManager.onConnectCallbacks; порядок init: лидерборды → storage → adaptive.
- **26. ConfigUpdater уничтожает пользовательские данные.** ✅ **СДЕЛАНО (ещё в fea15e6, подтверждено 2026-10-10):**
точечный merge (`ConfigMergeProcessor` + `ConfigMergeParser`): block-sequence листы и `ignoredSections` сохраняются,
есть `ConfigMergeProcessorTest` (249 строк). `performUpdate` ловит только `IOException`, runtime уходит на границу `ConfigLoader`.
- **27. SQL: утечка Hikari-пулов.** ✅ **СДЕЛАНО (ещё в fea15e6, подтверждено 2026-10-10):**
`StorageSQL.init(mode)` создаёт пул один раз (`if (connectionManager == null)`).
- **28. SQL: блокирующий connect на main thread** ✅ **СДЕЛАНО (ещё в fea15e6, подтверждено 2026-10-10):**
`setInitializationFailTimeout(-1)` — пул стартует, не дожидаясь БД.
- **29. SQL: адаптив не стартует.** ✅ **СДЕЛАНО (ещё в fea15e6, подтверждено 2026-10-10):**
`SQLConnectionManager.runWhenConnected` складывает колбэки в `onConnectCallbacks` вместо выбрасывания.
- **30. SQL: настройки игрока не сохраняются.** ✅ **СДЕЛАНО**: `SQLDataMapper.serializeSettings` пишет `PlayerSettingsManager.toSettingsMap(player)` (12 ключей) в JSON-колонку, `deserializeSettings` читает её тем же словарём (`normalizeNumericSettings` + `applySettings`) и падает на `applyDefaults` при пустоте/битости → `schematicDifficulty`/`blockLead` больше не null при входе (NPE в `updateGeneratorSettings`/`PAPIHook` не стало). Диск и SQL теперь используют **один и тот же** набор ключей.
- **31. Схематики: креш на main thread от битых файлов.** `LPSchematicLegacy`/`SchemLoader` парсят без валидации; NPE/AIOOBE мимо `catch(IOException)` (SchematicManager.java:109-133, LpschemConverter.java:44-54). Битый файл = отказ включения плагина. → Валидация dimensions/palette/frameCount + `catch(RuntimeException)` + лимиты размера файла/блоков.
- **32. JumpCalculator: возврат невалидного прыжка.** После 10 неудач валидация сдаётся и возвращает заведомо невалидный блок (JumpCalculator.java:125-136). → Пере-роллить offset/направление, а не только уменьшать distance/height; при исчерпании — safety-fallback (простой допустимый шаг).
- **33. JumpCalculator: диагональный heading залипает.** `JumpDirector.getRecommendedHeading` в безопасной зоне возвращает current без изменений (JumpDirector.java:105-107), а `calculateJumpOffset` при `offset.getX()!=0` перезаписывает Z (JumpCalculator.java:110-115) → курс зигзагом и не выпрямляется. → При диагонали аппендикс не перезаписывать случайным; корректный поворот-домножитель.
- **32. JumpCalculator: возврат невалидного прыжка.** ✅ **СДЕЛАНО (ещё в fea15e6, подтверждено 2026-10-10):**
`validateAndRetryJump` + `safetyFallback` (3 ре-ролла шага 1/0, затем прямой шаг без джиттера, в крайнем случае `current`);
покрыто `JumpCalculatorTest` (`safetyFallbackReturnsValidStepForUnitHeading`, `...ReturnsCurrentWhenStraightStepImpossible`).
- **33. JumpCalculator: диагональный heading залипает.** ✅ **СДЕЛАНО (ещё в fea15e6, подтверждено 2026-10-10):**
`offsetFor` считает перпендикулярный джиттер без перезаписи Z; покрыто `JumpCalculatorTest`.
### Генерация / lifecycle
@ -317,18 +330,17 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар
- **136. [M] Починить `IllegalCatch` (legacy, 22 / 19 файлов).** catch(Exception/Throwable/RuntimeException)→сужение до конкретных исключений либо переименовать в `expected|ignore`.
- **137. [M] Починить все нарушения PMD (45):** `CognitiveComplexity(17)` + `CyclomaticComplexity(7)` — декомпозиция; `GodClass(4)` + `TooManyMethods(8)` — декомпозиция; `AvoidReassigningParameters(7)` — переименование параметров; `EmptyCatchBlock(2)` — комментарий/конкретный тип.
- **138. [S] Перевести PMD из report-only в жёсткий гейт.** После зачистки убрать `isIgnoreFailures=true` в `build.gradle.kts` (pmd-блок). ✅ сделано в `b771cc1`.
- **169. [M] Починить `ReturnCount` (69 warning / 52 файла) — ПЕРЕСТРУКТУРИРОВАНИЕМ, не порогом.** Все 69 — это половина
правила про void (`maxForVoid` по умолчанию = 1 ⇒ запрещена любая guard-клауза `if (x) { return; }`); для value-методов
при `max=5` нарушений 0. Владелец запретил поднимать порог → в каждом из 69 void-методов убираем ранний `return`
(условие становится обычным `if`-блоком) либо выносим тело в helper-метод. Следить за `NestedIfDepth` (error, max=2)
и `NPathComplexity`. После обнуления — перевести `ReturnCount` в `error`.
- **170. [M] Починить `IllegalCatch` (23 warning / 18 файлов).** 19× `catch (Exception)`, 3× `catch (Throwable)`,
1× `catch (RuntimeException)`. Там, где конкретика реальна, — сужать до `IOException`/`NumberFormatException`/
`SQLException`/`YAMLException` и т. п. На истинных границах (диспетчер команд `LoParkourCommand:36`, `session-state:84`,
`LoParkour:128`) `Throwable` **обязан** стать `RuntimeException`: `Throwable` глотает `Error` (OOM, StackOverflow) —
это баг, а не стиль. Пограничные `catch (Exception)` в `onEnable/onDisable`, загрузчиках конфигов/схематчей и
`SchematicManager` — оставляем широкими осознанно (релоад плагина не должен ронять сервер), но каждый такой site
требует комментария «почему именно Exception»; после разбора правило остаётся в warning на незачищенном остатке.
- **169. [M] Починить `ReturnCount` (69 warning / 52 файла) — ПЕРЕСТРУКТУРИРОВАНИЕМ, не порогом.** ✅ **ВЫПОЛНЕНО (2026-10-10):** все 69 void-методов переструктурированы (guard-`return` → `if`-блок или helper: `placePlayer`/`tickPlayer`/`boostIfValid`/`applyCommand`/`firstNonNull`), порог не поднимался, `NestedIfDepth` ≤ 2 соблюдён; правило переведено в `error`. Попутно `NPathComplexity` 12 → 9. Checkstyle: **0 errors / 61 warning**, тесты 96/0 fail.
- **170. [M] Починить `IllegalCatch` (23 warning / 18 файлов).** ✅ **ЧАСТИЧНО (2026-10-10, остаток 18 — осознанные границы, правило остаётся в warning):**
3× `catch (Throwable)` → `catch (RuntimeException)` (диспетчер команд, session-tick, shutdown — `Error` больше не глотается);
сужено 5: `HoloHook` → `ClassNotFoundException | LinkageError`, `OptionGeneral` → `NumberFormatException` и
`IllegalArgumentException | IndexOutOfBoundsException`, `GeneratorProfileManager` → `IllegalArgumentException`,
`ConfigUpdater` → `IOException` (runtime уходит на границу `ConfigLoader`).
Не сужено осознанно (у каждого — комментарий «почему»): границы enable/reload/shutdown (`PluginBootstrap`,
`ConfigLoader`, `LocaleLoader`, `LoParkour:106`, `SQLConnectionManager:59` — таксономия ошибок HikariCP не публична),
порча данных (`InventoryData`, `PreviousData`, `SchematicManager` ×2), внешние API (`ScoreboardManager`/PAPI),
leave-путь (`UserRegistry`), изоляция колбэков (`SQLConnectionManager:106`), хот-путь генерации
(`JumpValidator`, `BlockSelector`), кэш сообщений (`LocaleCache`).
После зачистки п.132–137 — удалить соответствующие записи из `config/checkstyle/suppressions.xml` (супрессии остаются только для ещё незачищенных legacy-файлов).
@ -600,7 +612,7 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар
- **142. [P1] Убрать API, которых нет в старых/новых версиях.** Известно: `GameRule.*` и `Particle.*` разные (см. п.129: `Particle.FIREWORK` нет в 1.20.4), `Material.STONE_SLAB` (п.100), `BukkitRunnable.runTaskTimerAsynchronously` (п.21), `registerNewObjective(String,String,String)`. В 5.4.0-paper26 IP-Reborn заменял «removed Paper and Adventure API calls» — просмотреть их список как чек-лист.
→ Вынести версионно-зависимое в один слой `compat/` (определение версии по `Bukkit.getVersion()` — как `ref/ajParkour/.../utils/VersionSupport.java`; для 26+ учесть смену схемы версий: major ≥ 26).
- **143. [P1] Folia (п.21, issue #1).** `World.create()` уже не вызывает `createWorld` для загруженного мира (коммит «reuse already loaded world…»). Остаётся: `folia-plugin.yml`/`folia-supported: true`, region-scheduler вместо BukkitRunnable, настройки мира из global region. IP-Reborn сознательно *не* поддерживает Folia — честнее убрать бейдж, если не успеваем.
- **144. [P1] Заменить команду `/lp` — конфликт с LuckPerms.** LuckPerms использует `/lp`; у нас `plugin.yml`: `loparkour` с `aliases: [parkour, lp]`. Оба плагина борются за алиас, при загрузке в разном порядке один из них теряет команду. Заодно `parkour` конфликтует с плагином A5H73Y Parkour и с IP.
- **144. [P1] Заменить команду `/lp` — конфликт с LuckPerms.** ✅ **ВЫПОЛНЕНО (2026-10-10, решение владельца: `lp`→`lpk`, `parkour` оставить).** `plugin.yml`: `aliases: [parkour, lpk]`; `/lp` → `/lpk` в `README.md`, `docs/SCHEMATICS.md`, `src/main/resources/docs/SCHEMATICS.md`, `schematics.yml`, `CHANGELOG.md`, javadoc `ReplayCommandExecutor`. Права (`loparkour.*`) не менялись.
→ Убрать `lp` (и, вероятно, `parkour`) из `aliases`; оставить `loparkour` + короткий алиас без конфликтов (предложение: **`lpk`**; *имя ждёт решения владельца*).
→ Править: `src/main/resources/plugin.yml`; все `/lp …` в `README.md` (15), `docs/SCHEMATICS.md` (16), `src/main/resources/docs/SCHEMATICS.md`, `schematics/schematics.yml`, `docs/ARCHITECTURE.md`, локали, тексты сообщений/usage; `ReplayCommandExecutor` (javadoc `/lp replay`).
→ Права (`loparkour.*`) не менять. Добавить в CHANGELOG пометку о breaking-изменении алиаса; рассмотреть deprecation-сообщение при вызове старого алиаса, если он останется.