# 63. Ward vs Mana Disambiguation & Overlay HUD Display Architecture Specification

- **Mốc Thời Gian / Phiên Bản Tham Chiếu**: 15/09/2026 - POE2 0.3.x / Patch 6A9E477A
- **Trạng Thái**: COMPLETED & VERIFIED
- **Tác Vụ Giải Quyết**: Khắc phục triệt để hiện tượng Overlay hiển thị sai chỉ số Ward thành Mana và không hiển thị Mana thật sự của nhân vật.

---

## 1. Bối Cảnh & Phân Tích Nguyên Nhân Gốc Rễ (RCA)

### 1.1 Hiện Tượng
Khi bot vận hành trên nhân vật build Chaos Inoculation (CI) hoặc build sử dụng kết hợp Energy Shield + Ward:
- Overlay HUD trên màn hình hiển thị `Mana: 241/241` (trong khi 241 thực chất là giá trị Ward của trang bị).
- Ward không được hiển thị riêng rẽ trên overlay.
- Mana thực tế (ví dụ `897/897` hoặc pool mana tách rời) không được hiển thị hoặc bị đè bởi Ward.

### 1.2 Nguyên Nhân Kỹ Thuật Tầng Memory Engine (C++ Tier 1)
1. **Layout vùng nhớ LifeComponent trong Path of Exile 2**:
   - `+0`: Current HP, `+4`: Max HP (với CI là 1/1)
   - `+8`, `+12`, `+16`: Energy Shield (cur/max/unreserved)
   - `+24`, `+28`: Flat Mana pool (nếu character có mana inline; nhiều build CI hoặc end-game có giá trị rác `0xFFFFFFFF` hoặc 0 tại đây)
   - `+32`, `+36`: Inline Ward pool (`+32` = Current Ward, `+36` = Max Ward, ví dụ `241/241`)
   - `+88`, `+104`, `+112`, ...: Bảng con trỏ Components (chứa con trỏ tới `ManaComponent` độc lập)
2. **Khuyết tật trong `AutoDetectManaPool`**:
   - `AutoDetectManaPool(0)` quét các offset trong phạm vi `[-512, 1024]` quanh `m_playerAddr`.
   - Khi quét tới offset `+32`, hàm kiểm tra cặp `{+32, +36}` thấy `241/241` thỏa mãn điều kiện VitalStruct (`cur <= max`, `max >= 15`).
   - Do thiếu ràng buộc loại trừ Ward (`m_playerAddr + 32`, `m_expectedMaxWard`), hàm chấm điểm 1100 điểm cho offset `+32` và lập tức khóa `m_manaAddr = m_playerAddr + 32`.
   - Sau đó `AutoDetectWard` cũng phát hiện `241/241` tại `+32`. Dẫn đến cả hai con trỏ `m_manaAddr` và `m_wardAddr` cùng trỏ vào 1 vùng nhớ duy nhất (Ward).

### 1.3 Khuyết tật trong Tầng Hiển Thị Overlay (`src/core/main.cpp`)
- Hàm dựng text hiển thị overlay trước đây chỉ format 3 dòng chính: HP, ES, Mana.
- Không có dòng hiển thị Ward riêng biệt khi `m_wardAddr > 0`, khiến người dùng chỉ nhìn thấy dòng Mana giả mạo.

### 1.4 Khuyết tật trong Optical Sensor (`optical_hp_sensor.py`)
- `_detect_via_winocr`: `crop_r` trước đây bị cắt ngang ở `0.88 * h`, bỏ sót nửa dưới quả cầu Mana nơi các chữ số `876/876` hiển thị.
- `parse_vitals_text`: Khi OCR đọc được 3 cặp số trên cụm phòng thủ bên trái (`1/1`, `4284/4284`, `240/240`), vòng lặp fallback gán cặp thứ 3 cho `mana` thay vì `ward` (nếu không có nhãn chữ 'Mana' tường minh).

---

## 2. Kiến Trúc & Hợp Đồng Bất Biến (Architectural Invariants)

### Bất biến 1: Ràng buộc Bất Tương Đồng Ward - Mana (`INV-VITALS-WARD-MANA-EXCLUSION`)
- `m_manaAddr` TUYỆT ĐỐI KHÔNG được phép trỏ tới `m_playerAddr + 32` hoặc `m_playerAddr + 36` (vùng inline Ward của POE2).
- `AutoDetectManaPool` phải từ chối mọi ứng viên trùng với `m_wardAddr`, `m_wardAddr + 4`, `m_expectedMaxWard`, `m_shieldAddr`, `m_expectedMaxES`.
- Nếu phát hiện `m_manaAddr == m_wardAddr` hoặc `m_manaAddr == m_playerAddr + 32`, hệ thống tự động reset `m_manaAddr = 0` và kích hoạt tái dò tìm.

### Bất biến 2: Chuẩn Hóa Hiển Thị Đa Thuộc Tính Trên Overlay (`INV-OVERLAY-FULL-VITALS`)
- Overlay HUD trên màn hình in-game hiển thị đầy đủ và tường minh:
  ```text
  HP:     cur / max
  Shield: cur / max (nếu maxShield > 0)
  Ward:   cur / max (nếu maxWard > 0)
  Mana:   cur / max
  Spirit: cur / max (nếu maxSpirit > 0)
  ```
- Không bao giờ ẩn Ward hoặc gộp Ward vào nhãn Mana.

### Bất biến 3: Phân Vùng Crop OCR Chuẩn Xác 16:9 (`INV-OPTICAL-CROP-BOUNDS`)
- Cụm phòng thủ bên trái (Life, Shield, Ward): `crop_l = (0, 0.65*h, 0.25*w, 0.85*h)`. Giới hạn dưới `0.85*h` bảo vệ OCR không bị nhiễu bởi thanh Flask icons.
- Cụm tài nguyên bên phải (Mana, Spirit, Rage): `crop_r = (0.74*w, 0.66*h, w, 0.98*h)`. Mở rộng xuống `0.98*h` để bao quát toàn bộ đáy quả cầu Mana.

---

## 3. Chi Tiết Thực Thi (Implementation Details)

1. **`src/core/game_session.cpp`**:
   - Bổ sung `AutoDetectWard(m_expectedMaxWard)` chu kỳ 2000ms khi `m_wardAddr == 0`.
   - Bổ sung kiểm tra xung đột trong vòng lặp `GameSession::Update`: tự động giải phóng `m_manaAddr` nếu phát hiện chiếm dụng nhầm địa chỉ Ward.
   - Nâng cấp `evaluateCandidate` trong `AutoDetectManaPool`:
     - Bỏ qua offset 32 & 36 cả dạng flat và VitalStruct.
     - Kiểm tra loại trừ `m_wardAddr`, `m_expectedMaxWard`.
     - Thưởng điểm `+450` cho các ứng viên trích xuất từ Component Pointer (`Check2-DerefPtr` / `Check2-DerefVital`), bảo đảm Mana tách rời luôn được ưu tiên hơn các giá trị rác inline.
2. **`src/core/main.cpp`**:
   - Nâng cấp `overlay::SetLines`: hiển thị cả `Ward: cur/max` và `Spirit: cur/max`.
3. **`src/assistant_tool/optical_hp_sensor.py`**:
   - Chuẩn hóa `crop_l` tại `0.85 * h` và `crop_r` tại `0.98 * h`.
   - Cập nhật `parse_vitals_text`: nhận diện nhãn Ward và ngăn chặn gán nhầm cặp số thứ 3 của defense cluster cho Mana.
   - Thêm bước chụp OCR phụ cho `get_mana_roi` tại fallback Step 5.
4. **Bộ Kiểm Thử Phòng Ngừa Hồi Quy (Bug-Driven Regression Tests)**:
   - C++: Bổ sung Test 71 `TestWardManaDisambiguationAndNoFalseLock` trong `tests/test_memory_engine.cpp` kiểm chứng cả 2 kịch bản (Ward dò trước Mana, và Mana dò trước Ward) trên mock memory layout CI với Ward 241/241 và Component Mana 897/897.
   - Python: Bổ sung Case 5 & Case 6 trong `test_ci_optical_vitals_parsing_and_thousand_separators` kiểm chứng phòng vệ Ward không bị gán cho Mana.

---

## 4. Bằng Chứng Xác Minh (Verification Evidence)

1. **C++ Core Unit Tests (`AutoPOE2_Tests.exe` / CTest)**:
   - Lệnh: `ctest --output-on-failure -C Release`
   - Kết quả: `100% tests passed, 0 tests failed out of 1` (818/818 kiểm tra thành công, thời gian 36.75s).
   - Test 71 `Ward vs Mana Disambiguation & Inline Ward Protection`: PASS.
2. **Python Optical Sensor & GUI Suites**:
   - Lệnh: `pytest tests/test_optical_hp_sensor.py -v` -> 26/26 PASSED (55.63s).
   - Lệnh: `pytest tests/test_control_center.py tests/test_harness_scenarios.py -v` -> 14/14 PASSED (52.82s).
   - Tổng cộng: 40/40 test cases Python PASSED 100%.
