LoParkour/docs/ARCHITECTURE.md
loki5512344 9f70e2e586
refactor: remove legacy .lpschem format (TODO 117)
- Delete schematic/legacy/lpschem/ (LPSchematicLegacy + 5 pojo Gson models)
- Delete schematic/convert/LpschemConverter and /lp schematic convert command
- Remove SchematicClipboardBuilder.fromLegacy, convert branches in dispatcher/
  tab-completer/help, locales (ru/en), SCHEMATICS.md and yml comments
- Kept only .schem (WorldEdit) + .nbt
- Build green: checkstyle 0 errors, PMD 33
2026-08-05 18:58:18 +02:00

265 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Целевая архитектура LoParkour
> Руководство для последующих рефакторингов god-классов и добавления новых режимов (Duels, Elytra).
> Документ описывает **целевое** состояние; текущее зафиксировано в разделе «Текущее состояние».
---
## Принципы проектирования
1. **KISS** — минимум слоёв и абстракций для решения задачи; каждый класс делает одну вещь.
2. **DRY** — одна реализация фичи в одном месте; дублирование выносится в общий переиспользуемый слой
(напр. рендеринг/размещение блоков — в `block/`).
3. **SOLID**:
- **SRP** — у класса ровно одна ответственность (главный драйвер декомпозиции god-классов);
- **OCP** — новые режимы добавляются новыми подклассами/реализациями, без правки существующих;
- **ISP** — узкие интерфейсы (`Mode`, `MultiMode`, `GeneratorEventListener`);
- **DIP** — домен зависит от абстракций, инфраструктура зависит от домена.
4. **Композиция вместо наследования** — god-классы разбиваются на коллабораторов, инжектируемых в тонкий
фасад (паттерн уже применён в `ParkourGenerator`, `BlockPlacer`, `StorageSQL`, `Leaderboard` — развиваем его).
5. **Границы размера** (жёсткие, зафиксированы в `config/checkstyle/checkstyle.xml`, severity=`error`):
- файл ≤ **200 строк** (`FileLength`), метод ≤ **80 строк** (`MethodLength`),
параметров ≤ **7** (`ParameterNumber`), линия ≤ **150 символов** (`LineLength`),
`NPathComplexity` ≤ **50**, цикломатическая сложность ≤ **20**, вложенность if/for/try ≤ 3/2/2.
6. **Явный порядок зависимостей** — слои смотрят «вниз», циклы запрещены:
`api/` → `core/` (домен) → `block/` + subsystems (`generator/`, `schematic/`, `adaptive/`, `ghost/`,
`style/`, `world/`, `leaderboard/`, `reward/`, `duels/`, `elytra/`) → платформа
(`storage/`, `config/`, `hook/`, `listener/`, `command/`, `menu/`, `util/`).
7. **Новые файлы обязаны соответствовать всем правилам** — их нельзя добавлять в `suppressions.xml`.
Супрессии — только временный долг существующих legacy-файлов, удаляются по мере рефакторинга.
8. **Одна папка = одна цель.** Каждый пакет содержит файлы ОДНОЙ ответственности (пример нормы:
`command/schematic/` — все 6 файлов про схематик-команды). Если в папку попадают разные по смыслу
классы — режем на подпапки по под-целям (пример: `util/gui` — это часть подсистемы меню, а не общий
util; `mode/impl/` — соли-режимы, мульти-режимы и барьеры — разные цели). Ориентир: ~3-5 файлов на
папку; папка из 1 файла допустима, если это самостоятельная единица (util, model, msg), но НЕ ради
искусственной нарезки.
## Текущее состояние (кратко)
- **173 Java-файла** в `src/main/java/dev/loki/loparkour/` (база пакета `dev.loki.loparkour`), Java 21,
Paper 1.20.4 / Spigot API + Adventure, сборка shadow + checkstyle + PMD.
- Декомпозиция по паттерну «фасад + коллабораторы» уже частично проведена (`ParkourGenerator`,
`BlockPlacer`, `StorageSQL`, `Leaderboard`, `SessionStateManager`), но фасады и часть режимов всё ещё
превышают лимит 200 строк.
**God-классы (>200 строк) и где они «протекают»:**
| Класс | Строк | Протекающие обязанности |
|---|---|---|
| `command/LoParkourCommand` | 302 | роутинг по аргументам (0..4) + join/leaderboard логика + весь tab-complete |
| `command/schematic/SchematicCommandHandler` | 274 | сабкоманды схем + per-player кэш `SELECTIONS` |
| `config/core/ConfigUpdater` | 229 | шаблон-merge, YAML-парсинг full-path, бэкап/восстановление |
| `generator/jump/placement/BlockPlacer` | 227 | выбор BlockData, расчёт прыжка, запись блоков, эффекты, паста схем |
| `mode/impl/InvisibleBarrierMode` | 222 | `Mode`-фасад + `BarrierGenerator` + отрисовка контуров частицами |
| `hook/papi/PAPIHook` | 220 | метаданные expansion + роутинг + форматтеры рангов/сложности |
| `command/admin/AdminCommandHandler` | 214 | forcejoin/forceleave/reset/recoverinventory + резолв игроков |
| `adaptive/core/MetricsCollector` | 214 | метрики, near-miss-анализ, автосейв, кэш игроков |
| `storage/sql/StorageSQL` | 209 | инициализация пула, DDL, read/write скоров, read/write игрока |
| `player/core/ParkourPlayer` | 207 | Expose-настройки, скоринг-сет, статические реестры, сохранение, setup/hotbar |
| `leaderboard/core/Leaderboard` | 203 | CRUD, сортировка, тай-брейк, снапшоты рангов |
| `mode/impl/CoopMode` | 200 | на границе: `MultiMode` + `CoopGenerator` (contributions/милстоуны) |
Сопутствующие риски из `TODO.md`, влияющие на декомпозицию: статические mutable-поля
(`GeneratorState` — 15 публичных полей, `ParkourGenerator.session/island/profile/state/generatorOptions` —
п.107), рекурсия `unregister`/Coop (п.24), утечка SQL-пулов (п.27), per-player GUI-состояние (п.78),
неатомарный reload (п.70).
## Целевая структура пакетов
Новые режимы выносятся в **отдельные корневые пакеты** `duels/` и `elytra/` (не в существующий `mode/impl/`).
Общий переиспользуемый рендеринг/размещение блоков — в **`block/`**. Ядро домена — в **`core/`**.
Существующие пакеты сохраняются и встраиваются в слои.
```text
dev.loki.loparkour
│
├── api/ # Публичный контракт для внешних плагинов (единственный «экспортный» слой)
│ ├── core/ # ParkourAPI, Registry — регистрация режимов/стилей/событий
│ └── event/ # Доменные события (join/leave/score/fall/schematic/spectate/block-generate)
│
├── core/ # ЯДРО ДОМЕНА: агрегаты и доменные сервисы (слой ниже api/)
│ ├── model/ # Неизменяемые доменные модели (Score, Reward, GhostData и т.п.)
│ ├── player/ # Агрегат игрока: ParkourPlayer, ParkourUser, ParkourSpectator
│ ├── session/ # Session + жизненный цикл сессии (create/join/leave/removePlayers)
│ ├── mode/ # Контракты режимов: Mode, MultiMode, Modes
│ └── service/ # Доменные сервисы (UserRegistry, ParkourHotbar, ScoreboardManager)
│
├── block/ # ОБЩИЙ слой размещения/рендеринга блоков (для ВСЕХ генераторов и режимов)
│ ├── engine/ # BlockWriter: запись BlockData в мир + BlockEffectPlayer (particles/sound)
│ ├── selection/ # BlockSelector: выбор BlockData по стилям/весам (хук selectBlockData для режимов)
│ ├── style/ # BlockStyleProvider: материал/цвет/сдвиг блока по стилю
│ ├── jump/ # JumpCalculator, JumpValidator, JumpDirector, JumpOffsetGenerator, JumpType
│ ├── schematic/ # Паста схематик в мир (SchemPaster/StructurePaster, событие генерации)
│ ├── render/ # Частице-рендер: BlockOutlineRenderer, контуры/боксы (из InvisibleBarrierMode)
│ └── client/ # Клиент-сайд виртуальные блоки (без записи в мир) — использует Elytra
│
├── generator/ # SUBSYSTEM «бесконечная генерация» (сохраняется, использует block/)
│ ├── core/coordinator/ # ParkourGenerator (фасад), GeneratorProfileManager, GeneratorStatistics
│ ├── core/model/ # GeneratorState, Profile, Island, GeneratorOption
│ ├── jump/calculation/ # Расчёт прыжков (мигрирует в block/jump после стабилизации API)
│ ├── jump/placement/ # Оркестрация генерации блока (DEFAULT/SPECIAL/SCHEMATIC)
│ ├── lifecycle/loop/ # Тик/фолл/ресет/события генератора
│ ├── lifecycle/player/ # PlayerInteractionHandler, GeneratorCleanup
│ └── profile/ # GenerationProfileLoader (профили сложности)
│
├── schematic/ # SUBSYSTEM «схематики»: core/create/convert/nbt/schem/registry/legacy
├── adaptive/ # SUBSYSTEM «адаптивная сложность»: core, model, storage, bootstrap
├── ghost/ # SUBSYSTEM «ghost-реплей»: core (GhostManager/Recorder/Player), model
├── style/ # Визуальные темы: Style, RandomStyle
├── world/ # Зоны/острова: Divider, World
├── leaderboard/ # Лидерборды: core (Leaderboard/Sorter), model (Score), persistence
├── reward/ # Награды: Reward, Rewards
│
├── duels/ # НОВЫЙ РЕЖИМ «Duels» (корневой пакет, генератор на игрока)
│ ├── mode/ # DuelsMode implements MultiMode (create/join/leave, isAcceptingPlayers)
│ ├── generator/ # DuelsGenerator (owner) + SingleDuelsGenerator (генератор на игрока)
│ ├── match/ # goal, счёт, спавн-площадки, цвета игроков, initCountdown/win
│ └── menu/ # MultiplayerMenu / lobby invite (фильтр instanceof MultiMode)
│
├── elytra/ # НОВЫЙ РЕЖИМ «Elytra» (корневой пакет, клиент-сайд труба)
│ ├── mode/ # ElytraMode + вариации (Default/SpeedDemon/MinSpeed/TimeTrial/Close/Obstacle)
│ ├── section/ # ElytraSection, KnotDirector (сплайн/кольца), PointType
│ ├── render/ # Клиент-сайд рендер колец (sendBlockChanges)
│ └── generator/ # ElytraGenerator (свой tick()/fall-детект), фейерверк-буст
│
├── player/ # Существующий пакет (3-й слой, обслуживает core/player)
│ ├── core/ # ParkourPlayer, ParkourUser (агрегаты)
│ ├── data/ # PreviousData, InventoryData (save/restore состояния)
│ ├── service/ # ParkourHotbar, UserRegistry, ScoreboardManager, PlayerSettingsManager, BungeeUtil
│ └── spectator/ # ParkourSpectator
├── session/ # Session + Session*Manager (State/Player/User; тик пер-сессии)
├── mode/ # Существующие режимы: base/ (контракты), impl/solo|multi|barrier/
├── storage/ # Платформа хранения: Storage, disk/StorageDisk, sql/StorageSQL + SQL*
├── config/ # Платформа конфигурации: core, locale, options
├── hook/ # Платформенные интеграции: papi/ (+ resolver/), vault/, holo/, floodgate/
├── listener/ # Bukkit-листенеры: gameplay/, player/, schematic/
├── command/ # Команды: core/ (LoParkourCommand+Router+TabCompleter), util/, admin/, player/, schematic/
├── menu/ # GUI: core/, community/, lobby/, play/, settings/
└── util/ # Общие утилиты: gui/, item/, misc/, particle/, text/, world/
```
### Текущее состояние (после реструктуризации)
Правило «одна папка = одна цель» применено к переполненным пакетам:
- **`command/`** — `core/` (LoParkourCommand, CommandRouter, CommandTabCompleter — вход/роутинг/табы),
`util/` (CommandUtil), `player/` (PlayerCommandHandler, Join, Leaderboard), `admin/` (`ops/` — Force*/Reset/
RecoverInventory, `support/` — TargetResolver, Responses), `schematic/` (одна цель — схематик-команды).
- **`mode/impl/`** — `solo/` (DefaultMode, SpeedrunMode, GravityShiftMode), `multi/` (CoopMode, RaceMode,
SpectatorMode), `barrier/` (InvisibleBarrierMode, BarrierGenerator, BarrierRenderer).
- **`hook/papi/`** — `PAPIHook`+`PlaceholderDispatcher` (вход/диспатч), `resolver/` (Global/Player/ScoreRank
резолверы + PlaceholderFormatters).
- **`storage/sql/`** — корень (StorageSQL, SQLConnectionManager, SQLDataMapper), `schema/` (SQLSchemaManager,
SQLMigrationManager), `query/` (SQLQueryBuilder, SQLQueryExecutor), `repo/` (SQLPlayerRepository,
SQLScoreRepository).
- **`adaptive/core/`** — корень (AdaptiveDifficulty, MetricsCollector), `calc/` (DifficultyCalculator/
Adjuster/SkillAnalyzer), `metrics/` (MetricEventCollector, MetricsCacheStore, JumpTimingTracker), `model/`
(DifficultyWeights).
- **`api/event/`** — `session/` (Join/Leave/Spectate), `score/` (Score/Fall), `generation/` (Block/Schematic).
- **`config/core/`** — корень (Config, ConfigLoader, ConfigAccessor), `merge/` (ConfigUpdater,
ConfigMergeParser, ConfigMergeProcessor).
- **`config/options/`** — корень (Option, OptionGeneral), `section/` (Generation/Particles/SQL/Styles).
- **`player/service/`** — корень (UserRegistry, PlayerSettingsManager, BungeeUtil), `ui/` (ParkourHotbar,
ScoreboardManager).
- **`menu/core/`** — корень (LPMenu, Menus, MenuStub), `screen/` (MainMenu, DynamicMenu, ParkourOption);
`util/gui/` переехали в **`menu/gui/`** (GUI migration-стабы).
- **`schematic/legacy/lpschem/`** — ~~`LPSchematicLegacy` + `pojo/`~~ **удалено** (п.117 TODO): формат `.lpschem` выпилен, остались `.schem` (WorldEdit) + `.nbt`.
Пересекающие пакеты классы стали `public` (сигнатуры методов не менялись, только видимость). Оставшиеся
папки с 4–6 файлами — цельные по смыслу (jump-расчёты, block-placement, player-core, pojo и т.п.) — не дробятся.
## Карта миграции
Таблица «старый класс → решение» для god-классов. `split` — разделить на новых коллабораторов,
`keep` — оставить как есть, `move` — перенести ответственность в другой пакет без изменения поведения.
| Старый класс | Решение | Новые классы (пакет · ответственность) |
|---|---|---|
| `command/LoParkourCommand` | **split** | `command/CommandRouter` (диспетчер args.length 0..4) · `command/CommandContext` (sender/player/args-обёртка, сокращает параметры) · `command/JoinCommand` (join: режим/игрок/сессия) · `command/LeaderboardCommand` · `command/completion/CommandCompleter` (весь onTabComplete) · `command/completion/SchematicCompleter` + `ModeCompleter` (подкомандные) · `player/PlayerCommandHandler`, `admin/AdminCommandHandler`, `schematic/SchematicCommandHandler` — **keep** (уже выделены) |
| `command/schematic/SchematicCommandHandler` | **split** | `command/schematic/SchematicCommandRouter` (роутинг) · `.../SchematicCreateHandler` · `.../SchematicPasteHandler` · `.../SchematicWandSession` (per-player `SELECTIONS`, cleanup на quit — чинит п.84) · `.../SchematicConvertHandler` |
| `command/admin/AdminCommandHandler` | **split** | `command/admin/ForceJoinHandler` · `.../ForceLeaveHandler` · `.../ResetHandler` · `.../RecoverInventoryHandler` (по сабкоманде) · `command/admin/PlayerResolver` (resolveUUID/resolvePlayerName/findNearest; поддержка `BlockCommandSender` — п.119) |
| `config/core/ConfigUpdater` | **split** | `config/core/ConfigTemplate` (чтение шаблона) · `config/core/YamlPathParser` (full-path-парсинг, из `extractFullPathValues`) · `config/core/ConfigMerger` (точечный merge отсутствующих ключей, сохранение листов/комментариев — фикс п.26) · `config/core/ConfigBackup` (бэкап/восстановление) · `ConfigUpdater` = тонкий фасад (keep) |
| `generator/jump/placement/BlockPlacer` | **split+move** | `block/engine/BlockWriter` (placeBlockData, setBlockData+phys) · `block/engine/BlockEffectPlayer` (particles/sound на всех игроков) · `block/selection/BlockSelector` (**move**, уже есть) · `block/jump/JumpCalculator` (**move**, уже есть) · `generator/jump/placement/BlockGenerationOrchestrator` (generateSingleBlock, switch DEFAULT/SPECIAL/SCHEMATIC) · `generator/jump/placement/SchematicBlockGenerator` (tryGenerateSchematic) · `block/jump/TallMaterialResolver` (isTallMaterial + форм.высота) |
| `mode/impl/InvisibleBarrierMode` | **split+move** | `mode/impl/InvisibleBarrierMode` (keep, тонкий фасад) · `mode/impl/barrier/BarrierGenerator` (keep как есть, переезжает в подпакет) · `mode/impl/barrier/BarrierVisualState` (activeBarriers + blockColors + self-clean) · `block/render/BlockOutlineRenderer` (drawEdge/drawBlockOutline) · `block/render/ParticleBoxGeometry` (record Point + рёбра) |
| `hook/papi/PAPIHook` | **split** | `hook/papi/PAPIHook` (keep, тонкий: метаданные expansion + роутинг) · `hook/papi/PlaceholderRegistry` (плейсхолдер → resolver) · `hook/papi/PlayerPlaceholders` (score/time/lead/style/difficulty…) · `hook/papi/LeaderboardPlaceholders` (rank_*, leader/record) · `hook/papi/DifficultyFormatter` (parseDifficulty) · опционально `ScoreUntilPlaceholder` (score_until_N) · переименовать id `witp` → `loparkour` (п.118) |
| `storage/sql/StorageSQL` | **split** | `storage/sql/StorageSQL` (keep, фасад) · `storage/sql/SQLPool` (один Hikari-пул на плагин — фикс п.27, `setInitializationFailTimeout(-1)` п.28) · `storage/sql/SQLSchemaManager` (единый мигратор DDL — фикс п.62) · `storage/sql/SQLScoreRepository` (readScores/writeScores) · `storage/sql/SQLPlayerRepository` (readPlayer/writePlayer) · `storage/sql/SQLCallbackQueue` (runWhenConnected, фикс п.29) · `SQLQueryBuilder`, `SQLDataMapper`, `SQLMigrationManager` — keep |
| `player/core/ParkourPlayer` | **split** | `core/player/ParkourPlayer` (*keep* агрегат, тонкий) · `player/data/PlayerSettings` (Expose-поля настроек + их serialize/restore) · `player/service/ScoreTracker` (scoredBlocks/hasScored/markScored/blockKey) · `player/service/PlayerPersistence` (save/read) · `player/service/PlayerSetup` (setup/teleport/hotbar) · `player/service/GeneratorSettingsSync` (updateGeneratorSettings) · `player/core/ParkourPlayerRegistry` (статические getPlayer/getPlayers/isPlayer — заодно фикс O(n), п.47) |
| `leaderboard/core/Leaderboard` | **split** | `leaderboard/core/Leaderboard` (keep, фасад) · `leaderboard/core/ScoreComparator` (isBetterThan/тай-брейк) · `leaderboard/core/LeaderboardQueries` (getRank/getScoreAtRank/sort-снапшоты) · `LeaderboardStorage`, `LeaderboardSorter` — keep · **не split**: `model/Score.getTimeMillis` — bugfix парсинга `mm:ss.SSS`/`HH:mm:ss.SSS` и `long` (п.25) |
| `adaptive/core/MetricsCollector` | **split** | `adaptive/core/MetricsCollector` (keep: сценарии GeneratorEventListener) · `adaptive/core/JumpAnalyzer` (near-miss, время на блок) · `adaptive/core/MetricsAutoSave` (таймер автосейва, фикс Folia п.21) · `adaptive/core/PlayerMetricsCache` (кэш/выгрузка) · `adaptive/storage/StatsRepository` — keep |
| `mode/impl/CoopMode` | **split** (на границе 200) | `mode/impl/coop/CoopMode` (keep: MultiMode-фасад) · `mode/impl/coop/CoopSession` (создание лобби/инвайтов, создание сессии в create — фикс п.23) · `mode/impl/coop/CoopGenerator` (contributions/милстоны) · guard в unregister (фикс рекурсии п.24) |
## Новые режимы
Оба режима разрабатываются сразу в чистом виде (полное соответствие checkstyle, ноль PMD-нарушений) —
новая функциональность не может «перевозить» legacy-долг. Код режима НЕ добавляется в `suppressions.xml`.
### Duels — `duels/*` (в терминах задачи `mode/duels/*`)
Модель «генератор на игрока»: одна сессия-владелец, у каждого игрока свой генератор.
- **Пакеты:** `duels/mode/DuelsMode` (`implements MultiMode`), `duels/generator/DuelsGenerator`
(owner, `Map<ParkourPlayer, SingleDuelsGenerator>`, allowJoining, goal, spawnData),
`duels/generator/SingleDuelsGenerator` (`extends ParkourGenerator` со своим `state`/`history`/цветом/островом),
`duels/match/` (goal, счёт, initCountdown, win с guard `stopped`, идемпотентный синхронный `reset(false)`),
`duels/menu/MultiplayerMenu` + кнопка в `PlayMenu`.
- **Переиспользуют ядро:** `ParkourGenerator` (база), `BlockPlacer` + `block/*`, `PlayerInteractionHandler`
(скоринг по своей трассе), `ParkourHotbar` (баннер-старт), `PreviousData` (сейв/восстановление),
`Session`, `UserRegistry`. Результаты НЕ пишутся в лидерборд (`getLeaderboard() = null`).
- **Требования к ядру:** хук `selectBlockData()` (фиксированный цвет на игрока), пер-игроковая спавн-площадка
со сдвигом, ослабить `final class Island`, leave() без `ParkourUser.leave`-рекурсии (guard-флаг, п.24).
- **Не повторять баги Race/Coop:** без TIME-лидерборда (время брать своим форматтером из `state.start`),
ресет только синхронно с null-guard, сессия создаётся в `create()`, `join()` через `isAcceptingPlayers`.
### Elytra — `elytra/*` (в терминах задачи `mode/elytra/*`)
Один генератор на игрока, трасса = секции узлов → сплайн → «кольца»; **клиент-сайд рендер** без записи в мир.
- **Пакеты:** `elytra/section/ElytraSection`, `elytra/section/ElytraKnotDirector` (dx~N(75,15), dy-bias, dz~N(0,35)),
`elytra/section/ElytraPointType` (CIRCLE/FLAT), `elytra/generator/ElytraGenerator` (`extends ParkourGenerator`,
свой `tick()`, переопределённый `getMode()`, свой fall-детект — не полагаться на `LifecycleTickManager.checkPlayerFall`),
`elytra/mode/` (ElytraMode + Default/SpeedDemon/MinSpeed/TimeTrial/Close/Obstacle — по подклассу на режим),
`elytra/render/` (очередь `Map<chunkX, Set<Vector>>` → `player.sendBlockChanges(states)`, лимит min(viewDistance, 8)).
- **Переиспользуют ядро:** `ParkourGenerator` (база + профили сложности), `ParkourHotbar`, `PreviousData`,
`block/client/` (виртуальные блоки), зону `Divider`; скор — double (добавить float/double-вариант `Score` или
отдельный счётчик).
- **Требования к ядру:** пер-игроковый `state` (не разделяемый на сессию), высота/порог ASCEND под наши
zone-границы, раздача элитр в слот нагрудника через `ParkourHotbar`.
- **Зависимости:** Apache Commons Math (`SplineInterpolator`) или Catmull-Rom — без стороннего мира.
Тесты новых режимов (п.131): сплайн/секции, `ElytraGenerator.tick` (score по velocity.x, причины reset),
`DuelsGenerator.win` (guard/идемпотентность), `SingleDuelsGenerator.score→win` при goal, регрессия рекурсии unregister.
## Политика линтеров
- **Checkstyle — жёсткий гейт** (`isIgnoreFailures=false`, `maxErrors=0` в `build.gradle.kts`): правило `FileLength`
(200) и прочие жёсткие проверки падают сборку. Сейчас сборка проходит `0 errors`, но копит **~1429 warning**.
- **Шумные правила — severity=`warning`, не блокируют** (задано в `checkstyle.xml`): `MagicNumber`,
`FinalLocalVariable`, `EmptyLineSeparator`, `ClassDataAbstractionCoupling`, `ClassFanOutComplexity`,
`MissingDeprecated`. Они не требуют супрессий и чистятся отдельным проходом.
- **Legacy-нарушения — точечные супрессии** в `config/checkstyle/suppressions.xml` (`FileLength`,
`IllegalCatch`, `NPathComplexity`, `WhitespaceAround`). Каждая запись привязана к конкретному файлу,
удаляется сразу после декомпозиции/зачистки этого файла. **Новые файлы в супрессии не добавляются.**
- **PMD — report-only** (`isIgnoreFailures=true`), сейчас **45 нарушений**:
`CognitiveComplexity(17)` + `CyclomaticComplexity(7)` + `GodClass(4)` + `TooManyMethods(8)` — декомпозиция;
`AvoidReassigningParameters(7)` — переименование параметров; `EmptyCatchBlock(2)` — комментарий/тип.
После зачистки (п.137) `isIgnoreFailures` убирается, PMD становится гейтом (п.138).
- В `TODO.md` «Зачистка варнингов линтеров»: `FinalLocalVariable` ~1034 (чистить вместе с декомпозицией),
`MagicNumber` ~346, `WhitespaceAround` 30, `NPathComplexity` 29, `IllegalCatch` 22.
## Порядок работ
1. **Архитектура** — утвердить этот документ, зафиксировать целевое дерево пакетов и карту миграции.
2. **Декомпозиция god-классов** — по карте миграции (BlockPlacer → `block/*`, LoParkourCommand → роутеры,
PAPIHook → реестры плейсхолдеров, StorageSQL → репозитории + один пул, ParkourPlayer → сервисы,
InvisibleBarrierMode → `block/render`, ConfigUpdater → мержер и т.д.). После каждого файла — удаление его
супрессии из `suppressions.xml` (сборка обязана оставаться зелёной).
3. **Зачистка варнингов** — `FinalLocalVariable`/`MagicNumber`/`WhitespaceAround`/`NPath`/`IllegalCatch`
по отчётам `build/reports/checkstyle/`; закрыть оставшиеся PMD-нарушения.
4. **Elytra** — база + режимы + клиент-сайд рендер (меньше зависимостей, чем Duels; S/M-сложность).
5. **Duels** — `duels/*` с предварительными хуками ядра (`selectBlockData`, пер-игроковый state,
`final Island`); требует предварительно закрытого Coop-рекурсивного цикла (п.24).
6. **PMD-гейт** — убрать `isIgnoreFailures=true`, PMD в CI как жёсткий гейт; удалить пустые записи из
`suppressions.xml`.