# 16. RCA & FIX: NHẬN DIỆN SAI HP/ES/MANA/SPIRIT VÀ XYZ=(0,0,0) TRÊN CLIENT THẬT

> **Mã tài liệu**: `DOC-RCA-VITALS-XYZ-20260907-2030`
> **Mốc thời gian**: 07/09/2026 - 20:30 | **Phiên bản game**: POE2 v0.5.5 (client thật)
> **Bằng chứng đầu vào**: `bin/Release/core_log.txt` (phiên 19:26) — `HP=778/778 | ES=32763/54 | Mana=779/779 | Spirit=0/0 | XYZ=(0,0,0)`
> **Trạng thái**: [HOÀN THÀNH — POST-IMPLEMENTATION] **815/815 C++ checks PASS + 85/85 Python tests PASS** (chạy trực tiếp phiên này)

---

## 1. RCA — 5 NGUYÊN NHÂN GỐC RỄ (từ memory dump + log thật)

### 1.1. HP khóa nhầm block giả 778/778 (biến thể mới)
Dump thật quanh `0x269524F0270`: `[+0]=778 [+4]=778 [+8]=1 [+12]=32763 [+16]=54 [+20]=617 [+24/+28]=pointer...`
- Block này có **ECS signature {2,4} tại [-8]/[-4]** và **pointer "hợp lệ" tại +24** → lọt qua mọi gate cũ, kể cả magic check `maxHP==778 && hp==778` (line 765 cũ) vốn là **symptom patch** vi phạm Rule 8.
- Hệ quả dây chuyền: mọi heuristic ES/Mana/Spirit quét ±512 quanh block GIẢ này đều đọc rác.

### 1.2. ES = 32763/54: hardcode `offset==12 → +300 điểm` + read-path không có bất biến
- `AutoDetectShield` cộng 300 điểm cứng cho offset +12 → trúng cặp rác (0x7FFB, 54).
- Lúc lock có swap cur/max, lúc đọc trong `Update()` KHÔNG có → hiển thị đảo ngược 32763/54.

### 1.3. Spirit = 0/0: flat offset +36 hiện là thành phần của CON TRỎ
- Dump: `[+36]=617` = high word của pointer `0x269...` → `spMax` đọc ra ~1.38 tỷ → bị filter loại vĩnh viễn.

### 1.4. XYZ = (0,0,0) vĩnh viễn: Micro-Step tap W trong chế độ CLICK-TO-MOVE
- Log: `[LiveAdapt] Movement mode = MOUSE` nhưng Micro-Step bấm W → nhân vật không di chuyển → `FindMovedFromBaseline` < 3 ứng viên → kẹt vĩnh viễn ở `WAITING_FIRST_STEP`.
- Secondary: baseline chỉ 16MB quanh playerAddr — PositionedComponent có thể nằm ở heap khác.

### 1.5. Poisoned calibration từ Python: Optical Sensor quét màn hình THẬT bên trong `build_cmd()`
- `CoreController.build_cmd({"max_hp": 0})` tự chạy OpticalHPSensor quét desktop → đọc được số từ môi trường thật → truyền `--max-hp <rác>` xuống Core → ép khóa nhầm (targetMaxHP mode bỏ qua cross-vital gate).

---

## 2. GIẢI PHÁP ĐÃ TRIỂN KHAI (structural, thay toàn bộ magic number)

### 2.1. Bất biến ES cur (thay magic 778) — `game_session.cpp` + `player_finder.cpp`
- **Nguyên tâm**: layout PoE2 `{hp, maxHP, unreserved, ES cur(+12), ES max(+16), ...}` → **ES cur không thể > 20000**. Block giả thật có `+12 = 32763 (0x7FFB)`.
- `IsValidLifeComponent`: nếu `val12 > 20000` mà không có bằng chứng Mana pool (+24/+28) hoặc VitalStruct pointer (+88) → **REJECT**.
- `AutoScanHP` (auto mode): thêm gate `nextVal > 20000 && !hasMana → continue`; đồng thời siết Đường 2 "compact mana" (`nextVal <= 20000 && m2 <= nextVal`) để không nhận cặp rác (1, 32763) làm mana.
- **Đã XÓA** magic `maxHP == 778 && hp == 778` (an toàn cho nhân vật thật 778 HP).

### 2.2. ES read-path bất biến — `Update()`
- Đọc ES chỉ chấp nhận `esMax ∈ [1, 20000] && es <= esMax`; vi phạm → hiển thị 0/0 trung thực + đếm stale 60 tick → reset `m_shieldAddr` và dò lại.
- Xóa flat +12/+16 detect; `AutoDetectShield`: bỏ hardcode +12, cap `max <= 20000`, loại họ Life (±8 quanh maxHP).

### 2.3. Spirit scan-based — bỏ flat +36
- Sim: flat chắc chắn tại HPbase+24 (PlayerStateMemory).
- Thật: `AutoDetectSpirit()` mới — quét ±512, chỉ nhận `{cur, max}` với `max ∈ [10,1000], cur <= max`, loại họ Life (±16 quanh maxHP), **chạy sau khi Mana đã khóa**; threshold score >= 300. Không tìm được → 0/0 trung thực.

### 2.4. Micro-Step nhận biết MovementMode — `Update()` + `main.cpp`
- `GameSession::SetMovementMode()`: MOUSE → **click-to-move 1 bước** dưới tâm màn hình (qua KMBox interlock); WASD → tap W. Vẫn giữ 2 nửa interlock (`EnsurePoe2WindowFocus` + `m_allowPhysicalMove`).
- Fallback XYZ: kẹt > 6s với moved>=1 mà không tìm được triple → **mở rộng baseline 16MB → 64MB một lần** (throttle 250ms).
- `main.cpp` đã nối `SetMovementMode` ở startup + LiveAdapt.

### 2.5. Python: tách optical khỏi launch args — `core_controller.py`
- `--max-hp` chỉ truyền khi: (a) Manual Override > 0, hoặc (b) có `detected_max_hp` từ GUI, hoặc (c) bật opt-in `optical_inject_max_hp: true` (quét optical có chủ đích).
- **Mặc định KHÔNG quét màn hình bên trong `build_cmd()`** — triệt tiêu poisoned calibration + flaky test.

### 2.6. Build-stamp chống stale binary — `main.cpp`
- Ghi `[Core Build] <date> <time> | PID <n>` vào console + core_log.txt lúc startup → chẩn đoán ngay tình huống chạy nhầm binary cũ.

---

## 3. BẰNG CHỨNG KIỂM THỬ (Raw CLI Output, phiên 20:28 - 20:33)

| Bộ kiểm thử | Lệnh | Kết quả |
|---|---|---|
| C++ Core | `build\bin\Release\AutoPOE2_Tests.exe` | `[RESULT] TẤT CẢ 815 KIỂM TRA ĐỀU ĐẠT (PASS)` |
| Python Companion | `python -m pytest tests -q` | `85 passed in 62.25s` |

### Regression test mới (Rule 11) — `tests/test_core.cpp` Test 33 `TestVitalsStructuralRejection`
1. Tái hiện **đúng block giả thật** từ log 19:26 (778/778 + unreserved=1 + ES 0x7FFB/54 + pointer + signature {2,4}) → `IsValidLifeComponent` phải REJECT và `AutoScanHP(0,0,0)` cửa sổ hẹp phải trả 0.
2. Bất biến ES: `{105,200}` chấp nhận / `{32763,54}` đảo ngược phải bị `IsValidShieldAddress` từ chối.
3. Spirit: `AutoDetectSpirit` phải khóa đúng HPbase+24 (100/100) trên sim sau khi Mana khóa.

### Lưu ý vận hành
- **Binary runtime**: `bin/Release/AutoPOE2_Core.exe` (build 20:16, đã chứa toàn bộ bản sửa) — kiểm tra dòng `[Core Build]` trong log khi khởi động để chắc chắn không chạy binary cũ.
- **Offset thực địa**: Client thật cần 1 phiên chạy lại để xác nhận: HP khóa đúng block thật (log sẽ hiện địa chỉ KHÁC 0x...F0270), ES/Mana/Spirit đúng giá trị UI, XYZ khóa thành công sau micro-step click.
- Giới hạn thiết kế: ES max > 20000 (build ES cực đại) sẽ hiển thị 0/0 trung thực thay vì rác — chấp nhận theo triết lý "honest fallback".
