11 KiB
PayZoneC: прогрес-заповнення зони + два реальні знайдені баги (2026-08-12)
Користувач попросив візуальний індикатор заповненості Pay Zone (скільки з
RESOURCES_TO_CLOSE_ZONE вже занесено). Три ітерації дизайну (кожна —
реальна правка коду, не просто обговорення):
- Горизонтальний квад на землі, що росте вздовж world X — користувач: "заповнюється... з права на ліво" (не те, що хотів).
- Box, що росте вгору по Y (буквально "як вода в басейні") — виявився концептуально хибним: верх/низ box-а завжди займають весь footprint незалежно від висоти, тож з ізометричної камери зона виглядала "одразу заповненою" на будь-якій висоті > 0.
- Фінал: плаский квад на землі знову, але росте вздовж 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/master2026-08-12 (commit4a22a9b, "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, прогрес-бар зони), має власні "Перевірено" нотатки нижче за текстом.