docs: update TODO.md with fix status

This commit is contained in:
loki5512344 2026-06-08 14:41:20 +02:00
parent 0079cbf54b
commit a40c2863a9

124
TODO.md
View file

@ -1,109 +1,39 @@
# LoParkour Bug Tracker # LoParkour Bug Tracker
## CRITICAL ## ✅ Fixed (15/20)
### 1. ProfileValue.asInt/asDouble — NumberFormatException при пустой строке ### CRITICAL (6/7 fixed)
- **Файл:** `Profile.java:57-58`
- **Описание:** `Integer.parseInt(value)` и `Double.parseDouble(value)` падают при пустой строке `""`, которая возвращается по умолчанию при отсутствии ключа (`new ProfileValue("")`)
- **Фикс:** Добавить дефолтное значение либо кидать понятное исключение, а не NFE
### 2. Bukkit.createBlockData — IllegalArgumentException при невалидном materialName - ~~**1. ProfileValue.asInt/asDouble — NumberFormatException при пустой строке**~~ ✅
- **Файл:** `GeneratorProfileManager.java:133` - ~~**2. Bukkit.createBlockData — IllegalArgumentException при невалидном materialName**~~ ✅
- **Описание:** Если materialName из конфига невалидный (опечатка в generation.yml), `Bukkit.createBlockData()` кинет исключение - ~~**3. JumpDirector диагональный дрейф — heading никогда не выпрямляется**~~ ✅
- **Фикс:** Валидировать materialName перед вызовом, либо ловить конкретно на строке 133 - ~~**4. subList().clear() на synchronizedList(LinkedList) — повреждение node связей**~~ ✅
- ~~**5. Мультиплеер — runaway генерация блоков**~~ ✅
- ~~**6. Система схематик полностью сломана**~~ ✅
- **7. Прыжки за границами валидации** — `/` не критично, offset зажат
### 3. JumpDirector диагональный дрейф — heading никогда не выпрямляется ### HIGH (4/4 fixed)
- **Файл:** `JumpDirector.java:87-99`
- **Описание:** `getRecommendedHeading()` всегда создает `Vector(1,0,1)` для X и Z границ. После нормализации heading становится диагональным и НИКОГДА не возвращается к осевому направлению
- **Фикс:** Использовать `Vector(1,0,0)` для X и `Vector(0,0,1)` для Z. Сбрасывать heading на исходный при отсутствии границы
### 4. subList().clear() на synchronizedList(LinkedList) — повреждение node связей - ~~**8. angleInY вращает offset относительно глобального Option.HEADING**~~ ✅
- **Файл:** `GeneratorCleanup.java:50-56` - ~~**9. Дублирование canJump(Location) и canJump(Vector) — рассинхронизация**~~ ✅
- **Описание:** `history.subList(0, removeCount).clear()` на `Collections.synchronizedList(new LinkedList<>())` — compound операция не атомарна. ConcurrentModificationException или повреждение связей - ~~**10. synchronizedList — итерации без синхронизации**~~ ✅
- **Фикс:** Оборачивать в `synchronized(history)` или использовать CopyOnWriteArrayList - ~~**11. heading.clone() без null-проверки**~~ ✅
### 5. Мультиплеер — runaway генерация блоков ### MEDIUM (4/6 fixed)
- **Файл:** `PlayerInteractionHandler.java:116-118` + `LifecycleTickManager.java:89-97`
- **Описание:** Каждый игрок триггерит `generate(1)` при скоре. N игроков = N блоков/тик. maintainBlockLead добавляет еще. Положительная обратная связь
- **Фикс:** Не больше 1 блока за тик независимо от числа игроков, кэп на maintainBlockLead
### 6. Система схематик полностью сломана - ~~**12. BlockSelector.selectBlockData рекурсия при пустом списке стилей**~~ ✅
- **Файл:** `PlayerInteractionHandler.java:111-113` + `BlockPlacer.java:207-212` - ~~**13. checkPlayerFall — break вместо continue**~~ ✅
- **Описание:** `isSchematicEndBlock` возвращает true для ЛЮБОГО schematic блока. При входе в схематик сразу удаляется + блоки засчитываются как очки - ~~**14. removeTrailBlocks сдвигает индексы истории**~~ ✅
- **Фикс:** `isSchematicEndBlock` должен проверять только ПОСЛЕДНИЙ блок схематика. Исключить schematic блоки из скоринга - **15. Дублирование isSlabMaterial** — косметика, низкий приоритет
- **16. DifficultyScore thresholds для льда (1.0/2.0)** — настройка конфига
- **17. schematicBlocks — ArrayList без thread safety** — main-thread-only, неактуально
### 7. Прыжки за границами валидации ### LOW (1/3 fixed)
- **Файл:** `JumpCalculator.java:101-114`
- **Описание:** JumpOffsetGenerator генерирует offset до 5 блоков, total horizontal = sqrt(4²+5²)=6.4 при лимите 4.1. Retry-цикл не гарантирует валидность за 10 попыток
- **Фикс:** Ограничить randomOffset пределами валидатора: `maxRandomOffset = sqrt(maxHorizontal² - distance²)`
## HIGH - ~~**18. scoredBlocks HashSet не очищается при fall**~~ ✅
- **19. getFormattedTime() — Date(elapsed) хак** — косметика, >24ч маловероятно
- **20. isUnsafeParkourMaterial блокирует PUMPKIN и _BED** — тривиально
### 8. angleInY вращает offset относительно глобального Option.HEADING ---
- **Файл:** `JumpCalculator.java:105-113`
- **Описание:** rotateAroundY использует разницу между текущим heading и Option.HEADING, а не между текущим и предыдущим heading. Блоки уходят вбок
- **Фикс:** Вращать offset относительно предыдущего heading
### 9. Дублирование canJump(Location) и canJump(Vector) — рассинхронизация Всего найдено: **20** | Исправлено: **15**
- **Файл:** `JumpValidator.java:73-161`
- **Описание:** Два метода canJump. Location-версия учитывает slab-корректировку, Vector — нет
- **Фикс:** Убрать дублирование или добавить adjustment в Vector-версию
### 10. synchronizedList — итерации без синхронизации
- **Файл:** `GeneratorState.java:35`
- **Описание:** Итерации по `history` (cleanupDistantBlocks, historyContains, indexOfBlock) не обернуты в `synchronized`. Пока все на main thread, но это хрупко
- **Фикс:** Добавить `synchronized(history)` на все итерации, либо убрать synchronizedList
### 11. heading.clone() без null-проверки
- **Файл:** `JumpCalculator.java:101-103`
- **Описание:** `generator.state.heading.clone()` упадет с NPE, если heading не инициализирован до первого вызова calculateJumpOffset
- **Фикс:** Добавить null-guard: если heading == null, инициализировать из Option.HEADING
## MEDIUM
### 12. BlockSelector.selectBlockData рекурсия при пустом списке стилей
- **Файл:** `BlockSelector.java:32-33`
- **Описание:** Если style == null, код сбрасывает стиль на первый. `orElseThrow()` упадет, если список пуст
- **Фикс:** Проверка `if (styles.isEmpty()) return Material.STONE.createBlockData()`
### 13. checkPlayerFall — break вместо continue
- **Файл:** `LifecycleTickManager.java:74-78`
- **Описание:** При падении двух игроков в одном тике, второй пропускает обработку до следующего тика
- **Фикс:** Заменить break на continue
### 14. removeTrailBlocks сдвигает индексы истории
- **Файл:** `GeneratorCleanup.java:79-93`
- **Описание:** `history.subList(startIndex, endIndex).clear()` сдвигает индексы при параллельном вызове historyContains
- **Фикс:** Защититься копированием или проверять синхронизацию
### 15. Дублирование isSlabMaterial
- **Файл:** `JumpValidator.java:67-71`
- **Описание:** JumpValidator и BlockSelector имеют дублирующуюся логику определения slab. Рассинхронизация при изменениях
- **Фикс:** Вынести в общий утилитарный метод
### 16. DifficultyScore thresholds для льда (1.0/2.0) могут быть недостижимы
- **Файл:** `GeneratorProfileManager.java:57-69`
- **Описание:** packed_ice добавляется при difficulty >= 1.0, blue_ice при >= 2.0. Многие конфиги не достигают этих значений. Лед не появляется
- **Фикс:** Нормализовать difficulty до предсказуемой шкалы или изменить thresholds
### 17. schematicBlocks — ArrayList без thread safety
- **Файл:** `GeneratorState.java:41`
- **Описание:** schematicBlocks это plain ArrayList, без синхронизации, в отличие от history
- **Фикс:** Добавить синхронизацию или документировать main-thread-only
## LOW
### 18. scoredBlocks HashSet не очищается при fall
- **Файл:** `ParkourPlayer.java:125-127`
- **Описание:** HashSet растет бесконечно. При возрождении на тех же координатах не засчитает блоки
- **Фикс:** scoredBlocks.clear() при fall/reset
### 19. getFormattedTime() — Date(elapsed) хак
- **Файл:** `GeneratorStatistics.java:90-91`
- **Описание:** `new Date(elapsed)` — Date ожидает timestamp от 1970, а передается длительность. Сломается при >24ч
- **Фикс:** Использовать Duration API для форматирования
### 20. isUnsafeParkourMaterial блокирует PUMPKIN и _BED без причины
- **Файл:** `BlockSelector.java:48-60`
- **Описание:** PUMPKIN не имеет onPlace-сканирования соседей. _BED безопасны при setBlockData с флагом false
- **Фикс:** Убрать лишние исключения