# 05. Ghi chép Dò Địa chỉ HP trên Client Thật (HP Memory Hunt — thực địa)

> **Ngày**: 2026-09-05 | **Client**: PathOfExile.exe v0.5.5 (PID thay đổi mỗi lần đăng nhập)
> **Công cụ**: `bin/Release/AutoPOE2_MemProbe.exe` (tự nhắc UAC, đọc qua ReadProcessMemory elevated)
> **Kết quả**: ĐÃ ĐỌC ĐƯỢC BỘ NHỚ GAME THẬT và thu hẹp địa chỉ HP còn 14 mirror.

---

> [!WARNING]
> **HISTORICAL DOCUMENT — DO NOT USE AS IMPLEMENTATION REFERENCE**
> 
> The RVA `0x4434CE0` documented below was proven INVALID during live testing on 07/09/2026. It polled junk UI data (HP 2/250 bug, ES 125/0, Mana 2/256). This pointer chain has been **permanently banned** in code.
> 
> **Current production approach**: Deterministic heap scanning via `AutoScanHP` with `expectedMaxHP` validation. See [16_vitals_xyz_rootcause_fix_2026-09-07.md](../development/16_vitals_xyz_rootcause_fix_2026-09-07.md) and [vitals_calibration_architecture.md](../development/vitals_calibration_architecture.md).

---

## 1. Thông số thật đã đo (khớp docs/research/01)

| Hạng mục | Giá trị thật |
|---|---|
| Module base | `0x7FF70EB60000` (thay đổi theo lần chạy - ASLR) |
| Module size | `0x4C9D000` (~80 MB) |
| `.text` | VA=0x1000, 48.7 MB |
| `.rdata` | VA=0x2E86000, 12 MB |
| `.data` | VA=0x3A03000, 13.7 MB |
| PE header | `4D 5A 90 00` ("MZ") đọc thành công qua RPM |

## 2. Kết quả AOB scan (4 pattern trong tài liệu 06)

**Tất cả đều MISS** trên bản build thật → các pattern trong docs/development/06 chỉ mang tính
minh họa. Đường đi đúng đắn: differential scan (`scan-hp`).

## 3. Hành trình thu hẹp HP (nhân vật HP tối đa 42)

| Lượt | Phương pháp | Ứng viên còn lại |
|---|---|---|
| 1 | Full scan u32 == 42 | 195.791 |
| 2 | `--rescan-changed` (mất máu 42→32, bỏ addr vẫn = 42) | 4.235 |
| 3 | `--rescan 42` (HP hồi đầy, giữ addr == 42) | 58 |
| 4 | `--rescan-changed` (mất máu 42→34) | 19 |
| 5 | `--rescan 42` (hồi đầy) | **14** |

## 4. Cấu trúc 14 mirror tìm được

- Dãy địa chỉ liên tiếp **cách nhau 12 bytes**, bắt đầu `0x2875A965D60` (heap, đổi mỗi lần vào game):
  format mỗi entry: `[u32 HP][u32 0][u32 0xFFFFFFFF]`
- **Thí nghiệm phân loại** (mất máu → 12, đọc trong lúc hồi):
  - Slot 1-8 (`D60` → `DB4`): **HP SỐNG** — tăng real-time theo regen (12→24→25→35→42)
  - Slot 9-14 (`DC0` → `DFC`): snapshot lịch sử, đóng băng ở giá trị cũ, chỉ đổi khi bị đánh
- **Địa chỉ HP dùng được: `0x2875A965D60`** (slot đầu của nhóm sống)

## 5. Việc còn lại (lộ trình)

1. Tìm struct PlayerState thật (HP + maxHP + mana kề nhau) — dump vùng lân cận + scan
   pattern `{currentHP, 42, mana}` khi HP thay đổi.
2. Viết `--ptrscan <addr>`: quét `.data` module tìm static pointer → khối heap chứa HP,
   dựng pointer chain 2-3 cấp để sống sót qua restart → lưu `offsets.toml`.
3. Cắm chain vào `EntityManager` (đã có sẵn, backend-agnostic) → telemetry thật.
4. Lặp quy trình tương tự cho Mana / XYZ / Entity List.

## 6. Bài học kỹ thuật đã sửa trong phiên này

1. `ScanModule`: chunk cuối < overlap → `size_t` underflow → **vòng lặp vô hạn** (đã fix).
2. `std::string(wstr.begin(), wstr.end())` bị **hỏng bộ nhớ trong tiến trình elevated**
   → bắt buộc dùng `WideCharToMultiByte` (`ExeDirNarrow`) hoặc giữ path dạng wide.
3. `ofstream` phải kiểm tra `is_open()` và ghi `[SCAN DONE]` marker để điều phối từ ngoài.

## 7. Ket qua PtrScan (buoi chieu 2026-09-05)

Tool `--ptrscan <hexAddr>` hoan thanh: quet toan bo memory tim con tro tro vao
allocation chua target, phan loai static/heap, lap sau dan (Pass 1 -> 2 -> 3).

### Ket qua
- Pass 1: 68 con tro heap trung gian tro vao allocation `0x2875A740000` (span 32MB)
- Pass 2: 10.685 match -> 40+ chain cap 2, khong co static o tang 2
- Pass 3: chain sau hon, gom tap trung tai cum `0xB984A9E778` / `0xB984A9E960` /
  `0xB984A9EBD0` / `0xB984A9EC10` (vung `0xB984A9Exxx`)
- Vi du chain L3: `[[[0xB984A9E960]+0x42E8]+0x80ED] + 0xD0 -> HP`
- PID doi 25032 -> 14672 sau relog; base module `0x7FF70EB60000` giu nguyen (trung ngau nhien)

### Ket luan
- Chain tu HP ve goc **sau hon 3 tang heap** - root static chua lo.
- Vung `0xB984A9Exxx` la cau truc heap lon day dac con tro (nghi la player/UI root).

### Viec tiep theo
1. Chay ptrscan lap: `ptrscan B984A9E778` -> root moi -> lap lai den khi gap
   static that (A3 nam trong module range). Viet script tu dong vong lap nay
   (doc root moi tu report -> chay tiep).
2. Giam offset tolerance (0x1000 thay 0x10000) de giam nhieu.
3. Khi co static chain -> verify bang cach thoat game vao lai: chain phai con doc dung HP.
4. Luu chain vao `offsets.toml` -> `EntityManager` doc qua RPM backend.

## 9. Session relog thu 2 (PID 14672 -> 34188): PlayerState tim thay!

### Chu trinh hunt hoan chinh tren session moi
- scan-hp 21 (357.811) -> rescan-changed (109.655) -> [LOI: rescan 21 khi HP=10 -> 5 ung vien rac]
- scan-hp 10 (750.427) -> user hoi mau 42 -> rescan-changed 10 (4.602) -> user mat mau 33 -> rescan 33 -> **6 ung vien**

### PlayerState that xac dinh
Ung vien `0x502654103E8` co cau truc: `[currentHP][maxHP=42][maxHP=42]` lien ke
=> day la stat block cua nguoii choi (cac ung vien con lai la HP cua quai).
Luu y: HP POE2 KHONG tu hoi - gia tri on dinh giua cac thao tac => differential scan rat sach.

### PtrScan/PtrDeep tren PlayerState
- ptrdeep tolerance 0x10000 depth 8: BUNG NO nhieu (depth 5: 2.2M matches, frontier bao hoa 50k)
  -> khong tim duoc static sach. Bai hoc: tolerance 64KB qua rong.
- 8 static .data tu session truoc VAN hoat dong: *(S) = 0x501F0F64200 (vung root game,
  cap nhat live sau relog) nhung PlayerState cach ~105MB -> chain con nhieu tang nua.
- fmod chain (RVA 0x1FC528) DA XAC MINH chay dung trong session (doc dung HP 16) nhung
  GAY sau relog -> chi la trung hop.

### Chien luoc ke tiep (chot)
1. KHONG can static chain ngay: viet `--findplayer <maxHP>`: scan u32 pairs (X, maxHP, maxHP)
   => tim PlayerState tu dong MOI session (maxHP on dinh theo nhan vat).
2. Ket hop 8 static .data (RVA on dinh theo boot) + BFS tolerance 0x2000 co select
   de tim chain dai hon neu can.
3. Verify: relog -> findplayer phai tim lai duoc trong < 30 giay.

## 10. FINDPLAYER - Phuong phap chot da VERIFY thanh cong (2026-09-05 toi)

### Ket qua
- `findplayer 42` (quet full RAM, pattern {X<=42, 42, 42}): **3.051 stat block**
- User mat mau 42 -> 26, `findplayer 42 --rescan 26`: **CON DUY NHAT 1 dia chi:
  0x502654103E8**
- Verify doc truc tiep: `1A 00 00 00 | 2A 00 00 00 | 2A 00 00 00` = {HP=26, maxHP=42,
  maxHP=42} - TRUNG KHOP 100% voi man hinh game.
- Trung khop ca voi duong scan-hp doc lap truoc do (2 phuong phap doc lap -> 1 dia chi).

### Y nghia
- PlayerState block: HP@+0x00, maxHP@+0x04, maxHP copy@+0x08 (u32, little endian)
- Quy trinh auto-calibration MOI SESSION (khong can static chain, khong can UAC nhieu):
  1. Core startup: `findplayer <maxHP>` (~2-3 phut)
  2. User mat mau 1 lan, nhap HP moi: `findplayer <maxHP> --rescan <HPmoi>`
  3. Con 1 dia chi = PlayerState -> EntityManager doc HP/maxHP truc tiep
- Con lai: tim Mana (lan can trong cung block - dump rong hon), XYZ (scan float khi di chuyen).

## 11. MILESTONE: E2E HP THAT QUA IPC (2026-09-05 dem)

`AutoPOE2_Core.exe --backend rpm --player-addr 502654103E8` (elevated):

```
Player HP: 26/42   <- TRUNG KHOP HP that tren man hinh game
```

Toan bo chuoi hoat dong: Game RAM -> RPM -> PlayerState {26,42,42} -> Core 120Hz
-> SharedMemory Seqlock -> Python Agent. Spirit chua doc (0/0) - can mo rong struct.

### Code moi trong phien nay
- `IMemoryReader::ForEachReadableRegion` (abstraction liet ke region - SIM 1 region, RPM VirtualQueryEx)
- `PlayerFinder` (Scan/Rescan/ReadStats) - pattern {HP, maxHP, maxHP}, backend-agnostic
- `GameSession::CalibrateInteractive/Calibrate/SetPlayerAddress` + doc HP that moi tick
- Core flags: `--calibrate` (tuong tac console), `--player-addr <hex>` (truc tiep tu MemProbe)
- 444/444 unit test PASS

### Viec tiep theo
1. Tim Mana/ES trong PlayerState (dump rong quanh block - chung nam gan HP/maxHP)
2. Entity list: cung co che findplayer nhung cho quai (maxHP cua quai doi theo loai)
3. Sau do: G3 NavMesh -> G4 Reflex -> G6 FSM Full-Auto

## 12. XAC NHAN CUOI CUNG: Block HP cap nhat theo damage (2026-09-05)

User mat mau 26 -> 18, doc lai block:
```
0x502654103E8: 12 00 00 00 | 2A 00 00 00 | 2A 00 00 00
               = {HP=18, maxHP=42, maxHP=42}  <- TRUNG KHOP MAN HINH
```

### Ket luan chot
- Block {HP, maxHP, maxHP} cap nhat theo DAMAGE EVENT -> dung duoc cho Auto Flask
  (chinh la luc nhan damage ma can botoi mau!).
- Block KHONG track toa do (di chuyen khong lam doi floats +0x160) -> toa do XYZ
  can float differential scan (mode moi, chua lam).
- MemProbe read mode da ghi full hexdump vao report (de phan tich offline).

### Trang thai G2.3 (Player side): HOAN TAT
- HP/maxHP doc duoc tu game that qua RPM, chay qua IPC den Python (E2E verified 26/42).
- Con lai: Mana/ES (dump sub-block 0x50265411010), XYZ (float scan), Entity list.

## 13. Scan-float XYZ: tim thay triple, nhung per-session (2026-09-05 dem)

### Tool moi: scan-float
- `init2 <startHex> <sizeHex>`: quet float trong cua so [start, start+size] (loc 0.5-5000)
- `moved <delta>`: giu float da doi >= delta
- `still <delta>`: giu float khong doi (< delta)
- Bai hoc: allocation cua POE2 ~1GB+ -> scan init theo AllocationBase bi cap 256MB cat som
  -> phai dung init2 voi cua so bao quanh PlayerState.

### Ket qua tren session (PID 34188)
- init2 (64MB quanh PlayerState): 162.662 float
- moved 1.0 (sau di chuyen): 2.456
- Phan tich cum lien tiep (A, A+4, A+8): **18 triple XYZ**
- Triple 0x502657E0398: (0,-1.192,0) -> (29,3,3) sau di chuyen = NGO VI TRI
- Sau khi user di chuyen xa + level up (maxHP 42 -> 54): triple = 0 -> block stale

### Bai hoc
1. maxHP DOI khi level up (42 -> 54) -> findplayer phai truyen maxHP dong (da ho tro).
2. Dia chi per-session chet khi doi khu/level -> GIAI PHAP: auto-calibration trong Core
   (scan-float init tai startup -> doi 10s -> moved -> still -> moved -> XYZ tu dong,
   user chi can di chuyen khi duoc nhac).
3. HP qua findplayer van hoat dong (HP 18/26/33 deu doc dung qua cac lan verify).

---

## 14. Đột phá Cột mốc: Bi-Directional BFS & Chuỗi Con Trỏ Tĩnh Đã Xác Định (2026-09-06)

### 14.1. Thuật toán `CmdBridge` (Bi-Directional BFS Thu Hẹp)
- Sau khi `findplayer` xác định được stat block thật tại `0x502654103E8`, vấn đề nan giải là pointer scan thông thường bị bùng nổ tổ hợp (2.2 triệu match ở độ sâu 5 do tolerance 64KB quá rộng).
- Giải pháp: Xây dựng chế độ `bridge` trong `AutoPOE2_MemProbe` với:
  - Khung tìm kiếm thu hẹp: `0x400` (1 KB) quanh base của object thay vì 64 KB.
  - Thuật toán `mergeIntervals`: Gộp các đoạn nhớ chồng lấn thành các khoảng rời rạc liên tục để tìm kiếm nhị phân `std::upper_bound` không bao giờ bỏ sót con trỏ.
  - Giới hạn module chính: Chỉ dừng lại khi chạm vào ranh giới `[mainBase, mainBase + mainSize]` của `PathOfExile.exe`.

### 14.2. Chuỗi Con Trỏ Tĩnh (Static Pointer Chain) Đã Giải Mã Thành Công
Chỉ sau 2 Pass (Pass B1: quét con trỏ trỏ tới target, Pass B2: quét con trỏ trỏ tới các nút cấp 1), hệ thống đã tìm thấy đường nối tĩnh vào phân vùng `.data`:

```
PathOfExile.exe (Base: 0x7FF70EB60000, Size: 0x4C9D000)
       │
       ▼  + 0x4434CE0 (Static RVA trong phân vùng .data)
  [0x7FF712F94CE0] ─── Pointer Dereference ───► 0x50293D70010 (Heap Root Object)
       │
       ▼  + 0x48 (Offset cấp 1)
  [0x50293D70058]  ─── Pointer Dereference ───► 0x5026541023B (Life Component Base)
       │
       ▼  + 0x1AD (Offset cấp 2)
  [0x502654103E8]  ─── Final Address (Triple u32 Stat Block)
       ├── +0x00: currentHP = 26 (0x1A)
       ├── +0x04: maxHP     = 42 (0x2A)
       └── +0x08: unreservedMaxHP = 42 (0x2A)
```

### 14.3. Kiểm Chứng Số Liệu Thực Nghiệm
Đã sử dụng lệnh `AutoPOE2_MemProbe.exe read` trên tiến trình game đang chạy (PID 34188):
1. `read 0x7FF712F94CE0 16` $\rightarrow$ `10 00 D7 93 02 05 00 00` $\rightarrow$ Pointer `0x50293D70010`
2. `read 0x50293D70058 16` $\rightarrow$ `3B 02 41 65 02 05 00 00` $\rightarrow$ Pointer `0x5026541023B`
3. `read 0x502654103E8 16` $\rightarrow$ `1A 00 00 00 2A 00 00 00 2A 00 00 00` $\rightarrow$ HP=26, MaxHP=42, Unreserved=42!

### 14.4. Tự Động Hóa & Tích Hợp Vào Core Engine
- File cấu hình [bin/Release/offsets.toml](../../bin/Release/offsets.toml) tự động sinh:
  ```toml
  # Auto-generated by AutoPOE2 MemProbe Bridge
  [player_stats]
  player_addr = 0x502654103e8
  static_rva = 0x4434ce0
  num_offsets = 2
  offset_1 = 0x48
  offset_2 = 0x1ad
  ```
- `GameSession::Update` tự động nạp chuỗi con trỏ tĩnh mỗi frame và giải mã động.
- Kiểm thử IPC Shared Memory: Core Engine chạy 120Hz liên tục hơn 3.200 frame, script Python đọc chuẩn xác `Player HP: 26 / 42` với độ trễ < 0.2ms.


## 14. G4.1 AUTO FLASK - VERIFY THUC CHIEN HOAN TAT (2026-09-05 dem)

### Thong ke live combat (core_log.txt)
- 17 lan bam binh HP (phim 1) + 20 lan bam binh Mana (phim 2)
- maxHP doi 64 -> 76 giua session (level up) - block {HP, maxHP, maxHP} van track DUNG
  (doc 38/76) -> day la stat block SONG cua nhan vat, dia chi 0x502654103E8 on dinh
  ke ca level up!
- Mana block doc 19/64 live
- XYZ calibration van FAILED - viec con lai

### Fix trong phien
- Death detection: HP=0 -> tam dung flask + log 1 lan (tranh spam binh tren xac)
- Chan loi calibration: newHP >= maxHP -> tu choi (canh bao phai mat mau truoc)
- logger.hpp dung chung (main.cpp + game_session.cpp deu ghi core_log.txt)

### Trang thai G4.1: HOAN TAT + VERIFY LIVE
- HP flask (phim 1) + Mana flask (phim 2), nguong co dung, cooldown 800ms,
  death detection, core_log.txt chan doan
- Co che input hien tai: keybd_event (emulation) - nang cap Interception/Driver sau

### Viec tiep theo
1. XYZ calibration: retry logic + log chi tiet trong CalibratePositionInteractive
2. Mana flask thong minh hon (20 lan 19/64 khong tang -> binh co the het charge)
3. Entity list (pattern HP quai da biet tu hunt)
4. G3: NavMesh (can XYZ - phu thuoc muc 1)

## 15. BREAKTROUGH: ENTITY LIST DA TIM THAY (2026-09-06)

### Tool moi: `entscan <statAddr>`
- Pass A: ScanPointerPass tim moi o nho luu con tro tro vao [statAddr-256KB, statAddr]
- Pass B: voi moi (L, V): statOff = statAddr - V; di doc mang ±128 entry (stride 8),
  doc u32 tai (E + statOff) -> dem HP hop le (0 < hp <= 500)

### Ket qua (PID 34188, player HP=271 full)
- Array tot nhat: entries quanh 0x50265410038, statOff = 0x3B8
- 50 entry doc duoc, 22 entity song HP hop le
- Entity[0] = 0x50265410030 = PLAYER (HP=231, dung = statAddr - 0x3B8)
- ~21 monsters HP 1-14 (quai yeu dang farm)
- Verify toan hoc: statAddr(0x502654103E8) - ent0(0x50265410030) = 0x3B8 = statOff CHINH XAC

### Layout thuc da xac nhan
- Entity array: mang con tro u64, stride 8, player o index 0
- Entity base + 0x3B8 = currentHP
- Entity base + 0x3BC = maxHP (du doan theo pattern {HP, maxHP, maxHP} cua player)
- Entity base + 0x518 = floats (0.9, 42.0, -69.0) - snapshot copy (chua verify live)
- Name string KHONG nam trong 512 bytes dau - can tim qua pointer component

### Viec con lai
1. EntityManager: wire layout that (array walk + HP@0x3B8 + maxHP@0x3BC)
2. Static chain cho array (ptrdeep tren array slot) HOAC auto-calibration moi session
3. XYZ offset trong entity (verify bang scan-float per-entity)
4. Name offset (tim pointer den name component)
