# 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, прогрес-бар зони), має власні "Перевірено" нотатки нижче за текстом.