# Báo Cáo Khảo Sát & Kiến Trúc: Character Animation, Combat Feel & Control Responsiveness

> **Tác giả**: Teamwork Explorer (`explorer_survey_anim_1`)  
> **Nhiệm vụ**: Khảo sát mã nguồn Client WebApp FreeExile, hoạt ảnh nhân vật (Character Animation), cảm giác chiến đấu (Combat Feel), né đòn (Dodge Roll / i-frame), hit-stop, sát thương nảy (Floating Damage Numbers), và biến thể vung vũ khí (Weapon Swing Variations) chuẩn PoE2.  
> **Tiêu chuẩn tham chiếu**: [ENGINEERING_STANDARDS_2026.md](file:///c:/Projects/FreeExile/docs/standards/ENGINEERING_STANDARDS_2026.md) và [GEMINI.md](file:///c:/Projects/FreeExile/GEMINI.md).

---

## 1. Observation (Dữ Liệu Quan Sát Thực Tế)

### 1.1. Hiện Trạng Render & Hoạt Ảnh Nhân Vật
- **File**: [`client/webapp/js/engine/entity_renderer.js`](file:///c:/Projects/FreeExile/client/webapp/js/engine/entity_renderer.js) (418 dòng)
  * Dòng 308-316: Bóng nhân vật render bằng Canvas 2D ellipse (`ctx.ellipse(p.x, p.y + 6, 22 * shadowScale, 10, ...)`), scale theo nhịp bước chạy `player.stridePhase`.
  * Dòng 318-346: Chuyển động học thân hình nội suy đơn giản qua 4 biến: `pBob`, `torsoTilt`, `lungeX`.
    - `idle`: Nhấp nhô thở `Math.sin(player.animTime * 3.5) * 2.0`.
    - `run`: Nhún bước chạy `Math.abs(Math.sin(player.stridePhase)) * 4.0`, nghiêng thân `player.leanAngle`.
    - `attack`: 3 pha cố định (`t < 0.08` co tay, `t < 0.22` bổ đao lungeX=8, `t >= 0.22` thu hồi).
    - `dodge`: Nhấp nhô `pBob=6`, rướn người `torsoTilt=0.45`, lao tới `lungeX=12`.
  * Dòng 350-376: Thử gọi `AnimationEngine.drawEntityFrame(ctx, player.anim, ...)`. Nếu thất bại, fallback vẽ ảnh tĩnh `heroImg` (`char_sword_master.png` / `savage_primal_exile.png` kích thước $100 \times 125\text{px}$) bị xoay góc bằng `ctx.rotate(torsoTilt)`.
  * Dòng 378-401: Vệt chém kiếm (Slash Arc) được vẽ thủ công bằng Canvas 2D Arc (`ctx.arc(22, -45, 38, arcStart, arcEnd)`) với bán kính cố định 38px cho mọi loại vũ khí.

- **File**: [`client/webapp/js/engine/animation_engine.js`](file:///c:/Projects/FreeExile/client/webapp/js/engine/animation_engine.js) (295 dòng)
  * Dòng 15-26: `HERO_TRACKS` định nghĩa sprite atlas $1280 \times 960\text{px}$, kích thước mỗi frame $160 \times 192\text{px}$ (8 cột).
  * Danh mục hoạt ảnh nhân vật gồm 6 clip:
    1. `idle`: 4 frames (`idle_0` .. `idle_3`), 6 FPS, loop: true.
    2. `run`: 8 frames (`run_0` .. `run_7`), 12 FPS, loop: true, footsteps: [0, 4]. Đồng bộ tốc độ chạy thực qua `clip.fps * (velocity / baseSpeed)`.
    3. `attack_slash`: 6 frames (`attack_slash_0` .. `attack_slash_5`), 15 FPS, loop: false, hit_frame: 2.
    4. `skill_whirlwind`: 6 frames (`skill_whirlwind_0` .. `skill_whirlwind_5`), 18 FPS, loop: true.
    5. `dodge`: 6 frames (`dodge_0` .. `dodge_5`), 20 FPS, loop: false.
    6. `hurt`: 3 frames (`hurt_0` .. `hurt_2`), 12 FPS, loop: false.
  * Tệp atlas tồn tại thực tế: `client/webapp/assets/animations/hero_anim_atlas.png` ($743\text{ KB}$), kèm các atlas class phái (`char_sword_master`, `char_sword_maiden`, `char_feral_berserker`, `char_wild_archer`, `char_glacial_lancer`, `char_shadow_assassin`).

- **File**: [`client/webapp/js/engine/combat_skills.js`](file:///c:/Projects/FreeExile/client/webapp/js/engine/combat_skills.js) (490 dòng)
  * Cả 3 kỹ năng `doFire()` (dòng 236), `doThunder()` (dòng 257), `doFrost()` (dòng 278) đều kích hoạt **duy nhất một clip hoạt ảnh**: `AnimationEngine.playAction(player.anim, 'attack_slash', ...)`.
  * Không có hoạt ảnh đâm (thrust), đập đất (ground slam), hay niệm phép/bắn sét (lightning bolt). Hoạt ảnh `skill_whirlwind` hoàn toàn bị bỏ quên, không được gắn với bất kỳ kỹ năng hay nút bấm nào.

### 1.2. Hiện Trạng Huyễn Ảnh Bộ (Dodge Roll) & i-frames
- **Kích hoạt & Trạng thái**:
  * `combat_skills.js` (dòng 291-306): `doDodge()` gán `player.isIFrame = true`, `player.iFrameTimer = 0.25`, `player.animState = 'dodge'`.
  * `canvas_renderer.js` (dòng 113): Tăng $1.75\times$ vận tốc di chuyển khi `player.isIFrame === true`.
  * `collision_engine.js` (dòng 79-82): Cho phép nhảy vượt vật cản thấp (`prop.footprint.isLowObstacle`) khi đang né.
- **Vị trí kiểm tra Khung Bất Tử (i-frames)**:
  * Nơi duy nhất kiểm tra là [`client/webapp/js/engine/monster_system.js`](file:///c:/Projects/FreeExile/client/webapp/js/engine/monster_system.js) (dòng 284-300) trong hàm `executeMonsterAttackOnPlayer(m)`:
    ```js
    if (player.isIFrame) {
      if (typeof sfxEngine !== 'undefined') sfxEngine.playDodgeWhoosh();
      const pPos = worldToIso(player.wx, player.wy);
      if (typeof spawnDamageText === 'function') {
        spawnDamageText('NÉ ĐÒN! (i-frame)', pPos.x, pPos.y - 45, '#38bdf8', false);
      }
      return;
    }
    ```
  * Chưa có kiểm tra i-frame đối với cạm bẫy diện rộng (AoE hazard, ground lava/poison).
- **Hiện trạng Dư Ảnh (Ghost Trails / Afterimages)**:
  * Khởi tạo trong `canvas_renderer.js` (dòng 176-185): Mỗi frame trong 0.25s né đòn, hệ thống `push` 1 object mới `{ x, y, facing, alpha: 0.7, weapon }` vào mảng `player.ghostTrails` (gây 15-30 allocations mới mỗi lần né).
  * Render trong `entity_renderer.js` (dòng 30-43): Vẽ ảnh tĩnh `heroImg` mờ 45% mà **không vẽ frame hoạt ảnh hiện tại từ atlas**. Đồng thời gọi `player.ghostTrails.splice(i, 1)` gây rác GC liên tục.

### 1.3. Cơ Chế Xử Lý Va Chạm & Hit-Stop
- **Đường truyền đòn đánh**:
  * Nhấn Q, E, R không gây sát thương tức thì mà sinh đạn đạo bay: `vfx_slashes` (0.12s), `vfx_lightnings` (0.10s), `vfx_spikes` (0.14s) trong [`vfx_renderer.js`](file:///c:/Projects/FreeExile/client/webapp/js/engine/vfx_renderer.js).
  * Khi đạn đạo chạm mục tiêu (`progress >= 1.0`), gọi `hitMonster(elem, dmg, isCrit)`.
- **Hiện trạng Hit-Stop**:
  * **HOÀN TOÀN CHƯA CÓ HIT-STOP**.
  * Trong `canvas_renderer.js` dòng 7-8: `const dt = Math.min((now - lastTime) / 1000, 0.1); lastTime = now; player.animTime += dt;` chạy liên tục mà không có hệ số đóng băng thời gian `timeDilation` hoặc `hitStopTimer`.
  * Mục tiêu chỉ có rung lắc nhẹ `hurtTimer = 0.38` và rung màn hình `triggerScreenShake()`. Thiếu hoàn toàn cảm giác khựng lại 2-4 frame đanh thép chuẩn PoE2 khi đao kiếm chém xuyên thịt/xương.

### 1.4. Cơ Chế Sát Thương Nảy (Floating Damage Numbers)
- **File**: [`client/webapp/js/engine/iso_math.js`](file:///c:/Projects/FreeExile/client/webapp/js/engine/iso_math.js) (dòng 330-342):
  ```js
  const damageTexts = [];
  function spawnDamageText(text, x, y, color = '#fbbf24', isCrit = false) {
    damageTexts.push({ text, x, y, vy: -1.6, alpha: 1.0, color, isCrit, scale: isCrit ? 1.35 : 1.0 });
  }
  ```
- **File**: [`client/webapp/js/engine/vfx_renderer.js`](file:///c:/Projects/FreeExile/client/webapp/js/engine/vfx_renderer.js) (dòng 240-256):
  * Lặp mảng `damageTexts`, giảm alpha tuyến tính `alpha -= dt * 1.5`, bay lên đều `y += vy`.
  * Khi `alpha <= 0`, gọi `damageTexts.splice(i, 1)`.
  * **Không hề sử dụng Object Pooling**: Cấp phát động object `{ text, ... }` trên mỗi đòn đánh và dọn bằng `splice()`.
  * **Trường `scale` bị bỏ quên**: Dù được gán `scale: 1.35` khi bạo kích, mã nguồn trong `vfx_renderer.js` không hề áp dụng `ctx.scale`, chỉ đổi font-size cứng (`14px` vs `12px`).
  * Gọi `ctx.save()` và `ctx.restore()` riêng lẻ trên từng số sát thương gây sụt giảm FPS.

### 1.5. Giới Hạn Dòng Mã Nguồn & Vệ Sinh Code (Code Hygiene Gate)
- Kết quả chạy `python tools/lint/check_code_and_doc_hygiene.py --strict`:
  * `client/webapp/js/engine/combat_skills.js`: 489 dòng (Sát ngưỡng Hard Cap 500 dòng, còn 11 dòng).
  * `client/webapp/js/engine/monster_system.js`: 496 dòng (Sát ngưỡng Hard Cap 500 dòng, còn 4 dòng).
  * `client/webapp/js/engine/entity_renderer.js`: 417 dòng (Vượt Soft Cap 350 dòng).
  * `client/webapp/js/engine/iso_math.js`: 425 dòng (Vượt Soft Cap 350 dòng).

---

## 2. Logic Chain (Chuỗi Suy Luận Kỹ Thuật)

```
[Khảo Sát Thực Tế]
  ├─ 1. Sprite atlas & manifest có sẵn (idle, run, slash, whirlwind, dodge, hurt)
  │    nhưng Q, E, R đều dùng chung 1 clip 'attack_slash'
  │    ==> Cảm giác chiến đấu đơn điệu, không phản ánh chủng loại vũ khí (Đao, Thương, Phép).
  │
  ├─ 2. Huyễn Ảnh Bộ né đòn cấp phát object mới mỗi frame (15-30 obj/dodge)
  │    và chỉ vẽ ảnh tĩnh mờ 45% không khớp frame cử động
  │    ==> Vi phạm tiêu chuẩn Zero Allocation 120 FPS, dư ảnh kém thẩm mỹ.
  │
  ├─ 3. Đòn đánh chạm quái gọi ngay hitMonster() mà không có hitStopTimer
  │    ==> Vòng lặp render dt không khựng lại, đòn đánh cảm giác bay bổng (floaty), thiếu uy lực PoE2.
  │
  ├─ 4. Số sát thương spawnDamageText push/splice liên tục, scale bị bỏ quên, ctx.save() dày đặc
  │    ==> Rò rỉ hiệu năng GC, số bay tuyến tính buồn ngủ, không có hiệu ứng nảy bật (pop/bounce).
  │
  └─ 5. combat_skills.js (489 dòng) & monster_system.js (496 dòng) sắp chạm Hard Cap 500 dòng
       ==> KHÔNG ĐƯỢC nhồi thêm logic vung vũ khí vào các file này;
           BẮT BUỘC tách thành các module độc lập:
           - client/webapp/js/data/weapon_swing_catalog.js (danh mục biến thể)
           - client/webapp/js/engine/weapon_swing_renderer.js (render đường đao/thương/dậm đất/sét)
           - client/webapp/js/engine/combat_feel_engine.js (hit-stop, screen shake, pools)
```

1. **Từ Quan sát 1.1**: Sprite Atlases của 7 nhân vật và 2 loại quái đã tồn tại đầy đủ trong `client/webapp/assets/animations/`, nhưng runtime chỉ đang gọi `attack_slash` và `dodge`. Việc bổ sung biến thể chém (slash), đâm (thrust), dậm đất (slam), bắn sét (bolt) có thể tái sử dụng hạ tầng `AnimationEngine` kết hợp với các hiệu ứng vệt sáng (VFX Ribbon Trails) và biến dạng Canvas 2D chuyên biệt.
2. **Từ Quan sát 1.2**: i-frame đã hoạt động chuẩn xác trong `executeMonsterAttackOnPlayer`, nhưng hệ thống `player.ghostTrails` cần được chuyển sang **Object Pool tĩnh 32 phần tử**, đồng thời khi vẽ dư ảnh phải truyền frame hiện tại của atlas (`AnimationEngine.drawEntityFrame`) kèm bộ lọc màu quang phổ ma đạo (`globalCompositeOperation = 'lighter'`, tinting tím/lam `#38bdf8` / `#c084fc`).
3. **Từ Quan sát 1.3**: Hit-stop là yếu tố quyết định tạo nên "trọng lượng đòn đánh" (impact weight). Chỉ cần đưa biến `hitStopTimer` (0.03s - 0.06s tương đương 2-4 frame) vào `canvas_renderer.js`. Trong khoảng thời gian này, `dt` được nhân với hệ số đóng băng ($0.0$ hoặc $0.05$), giữ nguyên tư thế vung vũ khí và tư thế co giật của mục tiêu, kết hợp phóng đại vệt máu/tia lửa và micro camera push.
4. **Từ Quan sát 1.4**: Chuyển đổi `damageTexts` từ mảng động sang **Ring Buffer Object Pool 64 phần tử**. Bổ sung hàm quỹ đạo nội suy elip/parabol với hệ số phóng to ban đầu $\text{scale} = 1.6 \rightarrow 1.0$ (pop-in bounce), stroke viền đen tương phản cao và gom nhóm font state.
5. **Từ Quan sát 1.5**: Quy chuẩn kỹ thuật 2026 nghiêm cấm file logic vượt quá 500 dòng. Các file cốt lõi hiện tại đang cận kề ngưỡng đỏ (489-496 dòng). Giải pháp duy nhất bảo đảm an toàn kiến trúc là tạo mới các file module chuyên trách theo đúng chuẩn Native ES Modules.

---

## 3. Caveats (Các Điểm Giới Hạn & Giả Định)

- **Phạm vi điều tra**: Tập trung vào Client WebApp 2.5D (`client/webapp/`). Chưa khảo sát Native Swift Metal Client Engine (`client/apple_metal/`).
- **Sprite Atlas Frames**: Hiện tại Sprite Atlas của nhân vật (`hero_anim_atlas.png`) chỉ có 6 frame cho `attack_slash` và 6 frame cho `skill_whirlwind`. Chưa có sprite frames 2D riêng biệt cho tư thế cầm thương đâm (thrust) hoặc giơ búa dậm đất (slam). Do đó, giải pháp tối ưu là sử dụng **kỹ thuật lai ghép (Hybrid Animation)**: tận dụng frame cử động kết hợp biến dạng xương động học (Kinematic Torso/Arm transforms) và vệt vung vũ khí tương ứng (Procedural Weapon Trails & Ground Impacts).
- **Hệ thống HUD Orbs**: `index.html` hiện tại chỉ có thanh máu nhỏ ngang ở status card bên trái, chưa có 2 khối Cầu Sinh Mệnh/Nội Lực (Health/Mana Orbs) kiểu PoE2. Việc nâng cấp giao diện HUD sẽ cần tích hợp thêm markup DOM và CSS phù hợp.

---

## 4. Conclusion (Kết Luận & Thiết Kế Đề Xuất)

### 4.1. Ma Trận 4 Biến Thể Vung Vũ Khí (Weapon Swing Archetypes)

| Biến Thể | Vũ Khí Đại Diện | Pha Vung Đòn (Windup -> Hit -> Recovery) | Hiệu Ứng Thị Giác (Visual VFX) | Hit-Stop | Phản Hồi Rung (Shake & Haptic) |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **SLASH** (Trảm Kích) | Kiếm, Đại Đao (`azure_wind_sword`, `infernal_blade`) | Thu đao về sau ($0.06\text{s}$) $\rightarrow$ Vung cung bán nguyệt ($0.12\text{s}$) $\rightarrow$ Đứng thế ($0.08\text{s}$) | Vệt chém hình trăng khuyết đa tầng đỏ/lam, tia máu bắn ngang góc $45^\circ$ | 2 frames ($33\text{ms}$) | Shake $4.0$, Haptic `[25, 35]` |
| **THRUST** (Thấu Kích) | Thương, Dao Găm (`wpn_spiked_bone_spear`, `wpn_twin_fang_daggers`) | Hạ thấp trọng tâm ($0.05\text{s}$) $\rightarrow$ Đâm thẳng thần tốc ($0.08\text{s}$) $\rightarrow$ Rút thế ($0.06\text{s}$) | Chùm tia đâm xuyên hội tụ hình nón, sóng chấn động elip phía trước, tia lửa tóe điểm va chạm | 2 frames ($30\text{ms}$) | Shake $3.0$, Haptic `[40, 20]` |
| **SLAM** (Chấn Địa) | Trọng Phủ, Chùy (`wpn_dragonbone_maw_greataxe`) | Nâng búa quá đầu ($0.14\text{s}$) $\rightarrow$ Bổ thẳng xuống đất ($0.08\text{s}$) $\rightarrow$ Dậm gối ($0.12\text{s}$) | Vết nứt đất elip rung chuyển, mảnh đá vỡ văng tung tóe, vòng bụi khói nở rộng | 4 frames ($60\text{ms}$) | Shake $8.5$, Haptic `[60, 40, 60]` |
| **BOLT** (Lôi Phạt) | Trượng, Ma Đao (`wpn_etheric_demonstaff`, `wpn_abyssal_chainguard`) | Giơ cao thần binh tích tụ lôi vân ($0.10\text{s}$) $\rightarrow$ Giáng sét từ trời ($0.06\text{s}$) $\rightarrow$ Tản điện ($0.10\text{s}$) | Tia sét phân nhánh fractal ngẫu nhiên từ thiên không, quầng sáng lóa mắt, tia hồ quang nổ mặt đất | 2 frames ($35\text{ms}$) | Shake $6.5$, Haptic `[30, 45, 30]` |

### 4.2. Sơ Đồ Kiến Trúc Module Đề Xuất (Phòng Ngừa Vi Phạm Giới Hạn Dòng)

```mermaid
flowchart TD
    subgraph UI_And_Input [Tầng Điều Khiển & Giao Diện]
        InputController[joystick.js / Touch & Keyboard]
        SkillBar[Combat Skill Bar / Hotkeys Q, W, E, R, Space]
    end

    subgraph Data_Catalogs [Tầng Dữ Liệu Thuần - Catalogs <= 300 dòng]
        WeaponCatalog[weapon_swing_catalog.js\n- Archetype Specs\n- Combo Chains\n- Timers & Speeds]
    end

    subgraph Combat_Feel_Layer [Tầng Cảm Giác Tác Chiến <= 300 dòng]
        CombatFeelEngine[combat_feel_engine.js\n- Hit-Stop Freeze Timer\n- Directional Screen Shake\n- DamageNumberPool 64 slots\n- GhostTrailPool 32 slots]
    end

    subgraph Rendering_Layer [Tầng Hiển Thị Tác Chiến <= 350 dòng]
        WeaponSwingRenderer[weapon_swing_renderer.js\n- Slash Ribbon Arcs\n- Thrust Cone Beams\n- Ground Slam Craters\n- Fractal Sky Bolts]
        EntityRenderer[entity_renderer.js\n- Depth Sorting\n- Animated Sprite Frame\n- Tinted Ghost Trails]
    end

    SkillBar -->|Trigger Attack/Dodge| CombatFeelEngine
    InputController -->|Move / Aim| EntityRenderer
    CombatFeelEngine -->|Freeze dt / Hit-Stop| EntityRenderer
    CombatFeelEngine -->|Acquire Pool Slot| WeaponSwingRenderer
    WeaponCatalog -.->|Read Specs| CombatFeelEngine
    WeaponCatalog -.->|Read Visual Specs| WeaponSwingRenderer
```

### 4.3. Thiết Kế Chi Tiết Các File Cần Triển Khai

1. **`client/webapp/js/data/weapon_swing_catalog.js`** (~180 dòng):
   - Lưu trữ cấu hình 4 biến thể: `SLASH`, `THRUST`, `SLAM`, `BOLT`.
   - Lưu trữ ánh xạ vũ khí hiện có (`azure_wind_sword`, `infernal_blade`, `wpn_feral_bone_bow`, `wpn_spiked_bone_spear`, `wpn_twin_fang_daggers`) với chuỗi combo đánh thường và chiêu thức.
2. **`client/webapp/js/engine/combat_feel_engine.js`** (~220 dòng):
   - Quản lý `hitStopTimer`: Khi đòn đánh trúng mục tiêu, gọi `CombatFeel.triggerHitStop(frames, duration)`.
   - `DamageNumberPool`: Mảng 64 object cố định, quản lý vòng lặp với hoạt ảnh pop-in $1.6 \rightarrow 1.0$, không tạo rác bộ nhớ.
   - `GhostTrailPool`: Mảng 32 object cố định lưu trữ vị trí, hướng, frame index và thời gian sống của dư ảnh Huyễn Ảnh Bộ.
3. **`client/webapp/js/engine/weapon_swing_renderer.js`** (~260 dòng):
   - Vẽ vệt chém trăng khuyết (Slash Arc) theo tiến độ thời gian thực.
   - Vẽ tia đâm xuyên (Thrust Cone) kèm quầng sóng nhọn.
   - Vẽ vết nứt chấn địa (Slam Ground Crater & Debris) chiếu theo hệ tọa độ Isometric.
   - Vẽ tia sét phân nhánh (Fractal Lightning) với lõi trắng viền huyết sắc.
4. **Tích hợp nhẹ nhàng vào các file hiện tại**:
   - `canvas_renderer.js`: Thêm kiểm tra `if (CombatFeel.isHitStopped(dt)) { dt *= 0.05; }` tại đầu hàm `renderLoop()`.
   - `entity_renderer.js`: Thay thế vòng lặp `player.ghostTrails` cũ bằng `GhostTrailPool.render(ctx, dt)`.
   - `index.html`: Khai báo các script mới trước `combat_skills.js`.

---

## 5. Verification Method (Phương Pháp Kiểm Chứng Độc Lập)

Người nhận bàn giao (Sub-agents kế tiếp hoặc Host Agent) có thể kiểm chứng độc lập báo cáo này theo quy trình sau:

### 5.1. Kiểm Tra Kiểm Thử Đơn Vị (Unit Test Parity)
Chạy bộ kiểm thử tự động toàn dự án để bảo đảm baseline không bị phá vỡ:
```powershell
pytest tests/unit/ -v
```
*Điều kiện thành công*: $453/453$ bài test PASSED $100\%$.

### 5.2. Kiểm Tra Vệ Sinh Mã Nguồn (Code & Doc Hygiene Audit)
Chạy công cụ kiểm toán nghiêm ngặt để xác nhận số dòng của các tệp:
```powershell
python tools/lint/check_code_and_doc_hygiene.py --strict
```
*Điều kiện thành công*: Không có bất kỳ file mã nguồn nào vượt ngưỡng Hard Cap 500 dòng hoặc tài liệu vượt quá 600 dòng.

### 5.3. Kiểm Chứng Trực Tiếp Trên Trình Duyệt & Console
1. Mở file `client/webapp/index.html` trên trình duyệt Chrome/Safari.
2. Kiểm tra log Console để xác nhận các atlas tài nguyên đã tải thành công:
   ```javascript
   console.log(window.ASSETS['hero_anim_atlas']?.complete); // Phải trả về true
   console.log(window.AnimationEngine?.MANIFESTS['hero']);   // Phải trả về object manifest
   ```
3. Nhấn `Space` để né đòn và kiểm tra thẻ i-frame `#badge-iframe`.
4. Nhấn `T` hoặc click nút `⚔️ Thử Nghiệm Võ Học` để mở bảng telemetry, kiểm tra DPS và combo khi đánh bù nhìn Mộc Nhân Bất Hoại.

### 5.4. Tiêu Chí Bác Bỏ (Invalidation Conditions)
Báo cáo này sẽ bị coi là mất hiệu lực nếu:
- Phát hiện tệp `hero_anim_atlas.png` không tồn tại hoặc bị lỗi cấu trúc hình ảnh.
- Phát hiện mã nguồn `combat_skills.js` đã có sẵn cơ chế `hitStopTimer` (kiểm tra lại dòng 140-226 cho thấy hoàn toàn không có).
- Bổ sung mã nguồn trực tiếp vào `combat_skills.js` làm file vượt quá 500 dòng.
