Files
onboarding-project/docs/worklog/08-payzone-fill-progress.md
2026-08-14 15:03:43 +03:00

111 lines
11 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.
# PayZoneC: прогрес-заповнення зони + два реальні знайдені баги (2026-08-12)
Користувач попросив візуальний індикатор заповненості Pay Zone (скільки з
`RESOURCES_TO_CLOSE_ZONE` вже занесено). Три ітерації дизайну (кожна —
реальна правка коду, не просто обговорення):
1. Горизонтальний квад на землі, що росте вздовж world X — користувач:
"заповнюється... з права на ліво" (не те, що хотів).
2. Box, що росте вгору по Y (буквально "як вода в басейні") — виявився
концептуально хибним: верх/низ box-а завжди займають весь footprint
незалежно від висоти, тож з ізометричної камери зона виглядала "одразу
заповненою" на будь-якій висоті > 0.
3. **Фінал**: плаский квад на землі знову, але росте вздовж world Z (не X)
— з цієї камери (`CAMERA_OFFSET=(0,9,-9)`, дивиться в +Z) рух вздовж +Z
проєктується на екран як "знизу вгору", що і є тим "від краю до краю",
про яке просив користувач. Пінінг біля min-Z (найближчий до камери
край), колір `0x5ce65c` (зеленуватий).
## ⚠️ Знайдений і виправлений баг: депозит-політ інколи застигає в повітрі навіки
Користувач показав скріншот: статична текстура ресурсу, що просто висить
у повітрі. Трасував через тимчасове логування (spawn/complete ID на кожен
`playDepositFlight` — окремий `Tween`, не chain, коректно `TweenC.add()`-
нутий ПЕРЕД `.start()`) — підтвердив: рідко (не щоразу) `onComplete` просто
не спрацьовує, і клон застигає точно на прямій між HUD-точкою і зоною, на
довільному t. Причину всередині `tween.js`/`TweenC` не знайшов (це не той
самий баг з `.chain()` з Day 6 — тут узагалі нема chain). **Замість
подальшого копання — надійний дублюючий `setTimeout`**, що прибирає mesh
через `DEPOSIT_FLIGHT_DURATION_MS + 300мс` незалежно від того, чи
спрацював `onComplete`. Викликати `removeFromScene` двічі — нешкідливо
(другий викоик просто не знаходить батька для detach).
## ⚠️ Знайдений і виправлений баг: `"UI_Wood.001"` в коді не збігається з реальною назвою ноду
Користувач наполягав, що "текстура дерева" в центрі Pay Zone нікуди не
зникає, хоча по черзі виключив усі мої гіпотези (крейти всередині зони,
застиглий ресурс). Трасував живу сцену (`ThreeC.scene.traverse`, шукаючи
все з "UI_Wood" в імені, друкуючи ланцюжок `visible` від ноду до кореня) —
і ось воно: нод з raw glb JSON названий `"UI_Wood.001"` (з крапкою,
підтверджено прямим парсингом бінарника), але в ЖИВІЙ Three.js сцені після
завантаження він `"UI_Wood001"` (**без крапки** — щось у пайплайні
завантаження її стирає). `TestSceneC.createMap()` шукав
`map.getObjectByName("UI_Wood.001")` — рядок ніколи не збігався,
`getObjectByName` тихо повертав `undefined`, `if (woodIconAlt)` була
`false`, і `.visible=false` НІКОЛИ фактично не виконувався — попри те, що
код виглядав абсолютно правильним і "мав" би працювати. Виправлення:
рядок-літерал змінено на `"UI_Wood001"` (без крапки), підтверджено
трасуванням живої сцени, що тепер `visible=false` реально застосовується.
**Урок**: коли `if (identifiedNode)`-гард ЗАВЖДИ хибний (вузол ніби існує
в GLB, але код його "не бачить") — перевіряти РЕАЛЬНЕ ім'я в ЖИВІЙ сцені
(`scene.traverse` + лог імені), не тільки в сирому GLB JSON — завантажувач
може мовчки переписувати рядки (крапки, ймовірно, конфліктують з якоюсь
внутрішньою угодою іменування).
Побічно: під час полювання на цей баг зробив (і одразу відкотив на
прохання користувача) дві помилкові гіпотези-фікси — приховання
`UI_Interactive_Zone_02` (сам маркер зони) і видалення `Wooden_Box_016`/
`Wooden_Box_017` (2 крейти, що геометрично сидять майже точно в центрі
зони, ~0.18 і ~0.99 одиниць від центру — підтверджено обчисленням world
position прямо з GLB node transforms). Жодна з цих гіпотез не була
причиною — обидві повернуто як були. Крейти в зоні — це може бути окрема,
самостійна проблема левел-дизайну (вони справді там стоять), але це
свідоме рішення НЕ трогати без окремого прямого запиту користувача.
- Working tree на момент старту Day 5: `PlayerC.ts` мав незакомічені правки
(accel/decel рух, рефакторинг attack-facing на helper-и) — вони увійшли в
фінальну версію файлу (переписаний повністю, логіка руху збережена).
- Свідоме рішення: не рефакторити робочий AABB рух гравця під фізику; після
видалення тригерів у гравця взагалі немає cannon `Body` — тільки AABB.
- `ResourceC`/`BreakablePropC` — нові файли. `PlayerC.ts`/`TestSceneC.ts`/
`PhysicsC.ts` — модифіковані. `CameraFollowC.ts`/`ThreeC.ts` — не торкались.
`TriggerC.ts` — був створений і видалений в межах цієї ж сесії (див. "Файли").
- Закомічено й запушено в `origin/master` 2026-08-12 (commit `4a22a9b`,
"Add physics/combat foundation, resource pickup flow, VFX polish, and
HUD") — усе, що описано в цьому файлі до Day 8 включно, вже в репо.
`stats.html` (rollup-visualizer, артефакт білда) свідомо НЕ закомічено —
не вихідний код, генерується заново при білді.
- Якщо наступного разу знову треба буде правити combat: зона ураження —
та сама `INTERACTION_REACH`-аура, що й раніше використовувалась для
single-target пошуку (`findNearbyObstacle`, лишився для косметичного
idle-facing), просто тепер є окремий `findBreakableTargetsInZone` без
обмеження на кількість цілей.
- Day 6 (Tween): `TweenC.init()` викликається один раз в
`beforeResourcesLoadedCb.ts` — якщо колись переносити фізику/tween-бутстрап
в інше місце, не забути перенести й це, інакше всі `TweenC.add()` тихо
ні на що не впливають (сама `Group` існує, просто ніхто її не `.update()`-ить).
- `PayZoneC.init(payZoneNode, target)` МАЄ викликатись після `PlayerC.init()`
в `TestSceneC` (потребує реальний `PlayerC.object`, не просто позицію) —
уже виправлено (виклик з `TestSceneC.init()`, не з `createMap()`), просто
важливо не переносити назад.
- **Будь-який новий `.chain()` в цьому проєкті — додавай ОБИДВІ (усі) ланки
в `TweenC` окремо** (`TweenC.add(a); TweenC.add(b); a.start();`), інакше
повториться баг вище: друга ланка "грає" за прапорцем, але update() на
неї ніхто не кличе, і вона застигає навічно.
- **Важливий урок з цієї сесії**: "збір ресурсу" (`ResourceC`) і "передача
в Pay Zone" (`PayZoneC`) — два НЕЗАЛЕЖНІ механізми, і мають лишатись
такими. Не гейтувати `ResourceC.collect()` жодною умовою про гравця/зону
— це те, що вже одного разу зламало базовий збір (ресурси лежали на
землі й не зникали). Будь-яку майбутню умову про "стояння в зоні" чіпляти
тільки на стороні `PayZoneC` (він і так окремо стежить за
`getCollectedCount()` — `deposited`, тобто "хвостом" з незданого).
- **Ще один урок**: не вгадувати розмір/радіус тригер-зони на око зі
скріншота — рахувати з реальної геометрії (`Box3().setFromObject(node)`).
Саме це зламало `PayZoneC.isPlayerInside` (радіус=2 замалий за фактичний
прямокутник ~6×4.6, ще й не центрований на origin).
- Ручна перевірка (флеш/нахил крейтів, повне зникнення після 2 хітів на
стейдж, базовий збір ресурсів без прив'язки до зони, злив ресурсів у Pay
Zone тільки при стоянні там — тепер по реальному Box3, не вгаданому
радіусу, зникнення зони після `RESOURCES_TO_CLOSE_ZONE` доставлених
(100 на момент цього запису, зараз 20 — див. "прогрес-заповнення зони"),
відсутність зайвих текстур навколо Pay Zone) — стан на кінець Day 5/6;
усе, що сталось після (HUD, камера, VFX, прогрес-бар зони), має власні
"Перевірено" нотатки нижче за текстом.