# 28. Tool Độc Lập Zoom Camera & Xóa Sương Mù (Visual Tool)

- **Mã Tài Liệu**: `SPEC-DOC-28-STANDALONE-VISUAL-TOOL-ZOOM-FOG-20260911`
- **Mốc Thời Gian**: `11/09/2026 - 20:28:00`
- **Trạng Thái**: [CHUẨN HÓA KIẾN TRÚC — DUAL-LINE OVERLAY & ZERO-DRIFT SPECIFICATION]
- **Phạm vi**: `src/visual_tool/`, `src/core/memory/visual_mods.hpp/.cpp`, `src/assistant_tool/visual_tool.py`, `run_visual_tool.bat`, Control Center (nút mở tool, không còn checkbox Zoom/Fog trên Core)

---

## 1. Bài toán

Zoom Hack và xóa sương mù thể tích trước đây nằm trong **Control Center → F8 → Core** (`--zoom 2.2 --no-fog`). Hệ số bị khóa bởi slider GUI (tối đa 3.5x) và `ApplyZoom` **ghi cứng** `currentDistance`, nên camera nhảy tức thì thay vì người chơi tự chọn góc bằng **con lăn chuột**.

Yêu cầu:

1. Tách Zoom + xóa sương mù thành **tool riêng**, chạy độc lập với bot Core (F8).
2. Trần zoom tùy chọn, kẹp **[1.0x, 8.0x]**.
3. Trong game, **con lăn chuột** chọn góc nhìn hiện tại tới trần đó.
4. Core 120 Hz **không** bật Zoom/Fog mặc định — tránh hai tiến trình ghi đè camera.

---

## 2. Phân tầng (SSoT)

| Tầng | Tiến trình | Vai trò |
| :--- | :--- | :--- |
| Engine | `AutoPOE2_VisualTool.exe` | Admin, RPM, `VisualModsEngine`, hook con lăn, overlay, `Maintain()` 50 ms |
| GUI | `run_visual_tool.py` | CustomTkinter: trần zoom, fog, wheel-drive, trạng thái IPC |
| Thư viện | `VisualModsEngine` trong `AutoPOE2_Core_Lib` | Một nguồn logic ghi RAM (không nhân bản ctypes) |
| Core bot | `AutoPOE2_Core.exe` | Chỉ áp Zoom/Fog khi CLI **tường minh** `--zoom` / `--no-fog`. Không đọc `default_zoom` từ `offsets.toml`. |

Control Center chỉ còn nút **Mở Visual Tool**. F8 không truyền `--zoom` / `--no-fog`.

---

## 3. Hợp đồng zoom

Hằng số SSoT (`src/core/memory/visual_mods.hpp`, nhân bản khớp trong `visual_tool.py`):

| Hằng | Giá trị |
| :--- | :--- |
| `kVisualZoomMin` | `1.0f` (trần vanilla `maxDistance`) |
| `kVisualZoomMax` | `8.0f` (mở rộng góc nhìn toàn cảnh) |
| `kVisualZoomStep` | `0.10f` mỗi nấc con lăn (120 đơn vị Windows) |
| `kVisualZFarCullingScale` | `1.35f` |

Công thức bất biến (không đổi so với spec 02):

\[
\text{maxDistance} = \text{origMax} \times \text{cap},\quad
\text{zFar} = \text{origZFar} \times (\text{cap} \times 1.35)
\]

Hai chế độ:

| API | `currentDistance` | Dùng khi |
| :--- | :--- | :--- |
| `UnlockZoomCap(cap)` | **Không** ghi (để game / con lăn chọn) | Mở khóa trần |
| `StepZoom(delta)` | Ghi `origMax * current` (kẹp `[1.0, cap]`) | Wheel-drive điều khiển góc nhìn tự nhiên |
| `ApplyZoom(m)` | Ghi cứng triad | CLI Core `--zoom` (tương thích cũ) |

### 3.1. Cơ chế Phối hợp Mượt mà của `StepZoom` và `Maintain` (Decoupled Cap Architecture)

- **Nguyên nhân gốc rễ của lỗi kẹp cứng góc nhìn (Camera Clamp Lock Bug)**:
  * Trong phiên bản thử nghiệm ban đầu, `StepZoom` khi lăn chuột thu gần (ví dụ từ `3.0x` về `1.5x`) đã vội vã hạ `maxDistance` theo hệ số hiện tại (`WriteMaxAndZFar(m_currentZoomMultiplier)`).
  * Đồng thời, `Maintain()` lại đọc `capForLimits = m_wheelDriveActive ? m_currentZoomMultiplier : ...` và tiếp tục ghim `maxDistance` ở mức `1.5x`.
  * Hậu quả: Khi người dùng muốn lăn chuột lùi xa trở lại, engine nội tại của game thấy trần `maxDistance` bị kẹp cứng ở mức `1.5x * origMax = 67.5f`, dẫn tới việc **hoàn toàn không thể zoom xa trở lại** trần `zoomCap` ban đầu. Camera bị khóa chặt ở vị trí vừa thu lại.
- **Giải pháp Phối hợp Mượt mà Độc lập (Decoupled Ceiling Coordination)**:
  1. **Bất biến Trần Zoom (`INV-ZOOM-CEILING-DECOUPLE`)**:
     * `maxDistance` và `zFar` **LUÔN LUÔN** được mở khóa và duy trì ở trần tối đa `m_zoomCap`:
       $$\text{maxDistance} = \text{origMaxDistance} \times \text{m\_zoomCap}$$
       $$\text{zFar} = \text{origZFar} \times (\text{m\_zoomCap} \times 1.35\text{f})$$
     * `StepZoom(delta)` chỉ thay đổi và ghi `currentDistance` trong dải:
       $$\text{m\_currentZoomMultiplier} = \text{std::clamp}(\text{m\_currentZoomMultiplier} + \text{delta}, \text{kVisualZoomMin}, \text{m\_zoomCap})$$
       $$\text{currentDistance} = \text{origMaxDistance} \times \text{m\_currentZoomMultiplier}$$
     * `StepZoom` bảo đảm giới hạn trần luôn ở `m_zoomCap` (`WriteMaxAndZFar(m_zoomCap)`), tuyệt đối không kẹp hạ trần theo `m_currentZoomMultiplier`.
  2. **Trách nhiệm của `Maintain()` (50ms Loop)**:
     ```cpp
     // Luôn bảo đảm trần maxDistance được duy trì ở m_zoomCap
     const float capForLimits = (m_capUnlockOnly || m_wheelDriveActive) ? m_zoomCap : m_currentZoomMultiplier;
     const float targetMaxDist = VisualMaxDistanceTarget(m_origMaxDistance, capForLimits);
     const float targetZFar = VisualZFarTarget(m_origZFar, capForLimits);
     ```
     Nhờ cơ chế này, camera luôn có đầy đủ không gian vật lý để lùi xa hoặc tiến gần tự nhiên $100\%$ theo từng nấc con lăn chuột của người dùng mà không gặp bất kỳ lực cản hay hiện tượng kẹp cứng nào.

Ánh xạ con lăn khi POE2 đang foreground và wheel-drive bật:
- Lăn **xuống** ($\text{wheel} < 0$) $\rightarrow$ tăng hệ số $\Delta = +0.10\times$ (zoom xa), ghi `currentDistance` và duy trì `maxDistance = origMax * zoomCap`.
- Lăn **lên** ($\text{wheel} > 0$) $\rightarrow$ giảm hệ số $\Delta = -0.10\times$ (zoom gần), ghi `currentDistance` và **vẫn giữ trần `maxDistance` ở `zoomCap`** để khi cần người chơi có thể lùi xa trở lại tức thì.
- Con lăn trên cửa sổ GUI chỉnh **trần** (`zoom_cap`), không phải góc hiện tại.

---

## 4. IPC nhị phân 48 byte

Tên mapping: `AutoPOE2_VisualTool_v1` (session local).

```text
offset  type     field
0x00    u32      magic = 0x56543031  ('VT01')
0x04    u32      seqlock
0x08    f32      zoom_cap          GUI → engine
0x0C    f32      current_zoom    engine → GUI
0x10    u32      flags             GUI → engine
0x14    u32      status            engine → GUI
0x18    u64      camera_addr
0x20    u64      fog_addr
0x28    u32      pid
0x2C    u32      last_error
```

Cờ `flags`: bit0 enable zoom, bit1 disable fog, bit2 wheel-drive, bit3 panic restore, bit4 yêu cầu AutoDetect lại.

`status`: bit0 attached, bit1 camera_ok, bit2 fog_ok, bit3 engine_running.

---

## 5. Khởi chạy

- `run_visual_tool.bat`: tự nâng Admin (RPM), mở GUI; GUI spawn `AutoPOE2_VisualTool.exe` nếu chưa chạy.
- CLI engine: `--max-zoom 8 --no-fog --wheel --no-overlay`
- Panic: `Pause/Break` hoặc `Ctrl+Shift+F12` → `PanicRestoreAll()` trong 1 frame; đóng tool cũng hoàn nguyên.
- Mutex `AutoPOE2_VisualTool_Singleton`: một engine.

Hot path Core 120 Hz không mở mapping này.

---

## 6. Kiểm thử khóa hợp đồng

- `tests/test_core.cpp` — `UnlockZoomCap` không ghi `currentDistance`; `StepZoom` kẹp [1, cap] ≤ 8; `Maintain` cap-mode không ép current; **Test 40c** AutoDetect FOV=10° / min=5, AOB `mov rax,[rip]`, từ chối `camera_addr` rác và buffer `"output"` + `zFar=810`.
- `tests/test_visual_tool.py` — toán con lăn/cap, IPC 48 byte, GUI, Core F8 không gắn `--zoom`.

---

## 7. AutoDetect trên client 11/09/2026

Hiện trường (PID 11644, `PathOfExile.exe` ~76.6 MB, Visual Tool gắn được PID nhưng `camera_addr=0`, `fog_addr=0`):

- AOB singleton kề `movss [rcx+2C]` + `movss [rcx+34]` **không còn** trong `.text`.
- Clamp zoom live: `cmp byte [rax+2Fh],0` rồi `movss xmm0,[rax+34]` — layout `maxDistance=+0x34` vẫn đúng; instance nằm trên heap (`[rbx+0x20]` trong mảng stride `0x30`).
- Heuristic cũ (`min∈[10,25]`, `max∈[35,60]`, FOV radian `[0.4,1.4]`) bỏ sót camera khi FOV ghi `10.0f` (độ) hoặc `minDistance` hẹp.

`AutoDetect` (11/09/2026): đa AOB + deref pointer, structural **bắt buộc min + FOV** (không khóa typical+zFar), fog chỉ nhận cụm 4 float `FogConstants`, `ScanModule` bỏ chunk lỗi, log `visual_tool.log`, overlay không báo "Đã gắn" khi chưa có camera.

Hiện trường PID 3132 (10:43, 11/09/2026): IPC `camera_ok=1` nhưng object `0xb25bdeee80` là buffer `"output"` (`vtable=0x0F`, `min/FOV/zNear=0`, `zFar=810` do tool ghi). Zoom **không** đi qua GPU scheduling / NIS / DirectX 12 của Windows — ghi RAM nhầm object nên hình không đổi. Zoom/fog **không** persist heap vào `offsets.toml`.

---

## 8. Build `6AA2213C` (vá 11/09/2026) — bắt buộc RE lại camera

Fingerprint live (Test 60): `TimeDateStamp=0x6AA2213C`, `SizeOfImage=0x4C9E000`, `.data` VA=`0x3A04000`, `LayoutHash=0x3C619311AE16D3D6`. AOB singleton `lea/mov +2C/+34` **0 hit**. Quét 651 MB heap (11:15): **0** cụm vanilla `min=15 max=45`, **0** FOV `π/4`.

IPC lúc 11:15 vẫn `camera_ok` trên object `0x2880c5e2180` — `qword[0]` là bit-pattern float (không phải vtable canonical), `cur=max=180` (tool ghi), `zNear=8` — đây là **ma trận/transform**, không phải `POE2_CameraInstance`.

RE có chủ đích (không cần extract Ghidra 2h cho Visual Tool): hàm copy camera `0x7ff6afe823e0` (một caller `0x7ff6afe82219`) vẫn ghi `this+0x2C/+0x30/+0x34/+0x38/+0x40`. Layout class đó còn; **chưa** khóa được instance heap. Heuristic bắt buộc vtable canonical user-mode + `zNear ∈ [0.001, 5]`. Overlay **Chưa thấy camera** là đúng cho đến khi dò xong instance.

Full pipeline Ghidra `re_extract.py` cho InGameState/Core là việc **tách** (INV-BUILD-01); Visual Tool chỉ cần instance camera heap.

---

## 9. Kiến trúc Code Patching trong phân vùng `.text` (Native Smooth Zoom & Zero Rubberbanding)

### 9.1. Phân tích Nguyên nhân Gốc rễ (Root Cause Analysis - RCA) của hiện tượng Camera Giật / Rubberbanding

Trên các bản build thực tế của Path of Exile 2 (đặc biệt các bản cập nhật tháng 09/2026), cơ chế can thiệp thuần Heap (Data-Only Heap Polling) bộc lộ hai khiếm khuyết mang tính cấu trúc dẫn đến hiện tượng camera bị giật cục, nảy qua lại (rubberbanding) hoặc mất hoàn toàn điều khiển:

```
[Vấn đề 1: Tranh chấp tần số]
Game Render Loop (144 FPS ~ 6.94ms):
  └─► Thực thi mã máy .text nội tại: cmp byte [rax+2Fh],0 ──► Kẹp currentDistance <= maxDistance (45.0f)
            ▲                                                                │
            │ (Tranh chấp trạng thái liên tục 144 lần/giây)                   │
            ▼                                                                ▼
Visual Tool Maintain() (20 Hz ~ 50ms):                               [CAMERA GIẬT CỤC /
  └─► WPM ghi đè maxDistance = 180.0f, zFar = 810.0f ──────────────►  RUBBERBANDING]

[Vấn đề 2: Lỗi thẩm định CameraLooksValid & Detection Thrashing]
CameraLooksValid() gọi thiếu moduleBase/moduleSize (mặc định = 0)
  └─► vtableOk = false (mất 3 điểm)
  └─► Khi zoom > 2.67x (maxD > 120.0f): typical = false (mất tiếp 3 điểm)
  └─► Tổng điểm <= 4 < 5 ──► CameraLooksValid() trả về FALSE!
  └─► Kích hoạt camInvalid ──► SetCameraAddress(0) ──► Kẹt trong vòng lặp AutoDetect vô tận!
```

1. **Tranh chấp chu kỳ giữa External WPM Polling (50ms) và Game Render Loop (144 FPS)**:
   - Tool ngoài vận hành vòng lặp `Maintain()` ở chu kỳ $50\text{ ms}$ ($20\text{ Hz}$) thông qua `WriteProcessMemory`.
   - Trong khi đó, Render Loop của game POE2 chạy ở tốc độ khung hình cao ($144\text{ FPS} \approx 6.94\text{ ms}$, hoặc $120\text{ FPS} \approx 8.33\text{ ms}$).
   - Trong từng frame render, game engine thực thi đoạn mã máy nội tại kiểm tra điều kiện kẹp camera:
     ```assembly
     cmp byte ptr [rax+2Fh], 0     ; Kiểm tra cờ clamp camera
     jnz short loc_clamp           ; Nếu có clamp, nhảy tới khối giới hạn
     movss xmm0, dword ptr [rax+34h] ; Nạp maxDistance nội suy từ game
     comiss xmm1, xmm0             ; So sánh khoảng cách hiện tại với trần
     ```
   - Do tần số thực thi của game nhanh gấp hơn 7 lần tốc độ ghi của tool ngoài ($144\text{ Hz}$ so với $20\text{ Hz}$), game liên tục cưỡng chế kéo `currentDistance` về giới hạn mặc định của frame, tạo nên xung đột ghi đè liên tục (State Race / Tug-of-War), khiến người chơi nhìn thấy camera bị rung giật (jittering/rubberbanding) liên hồi.

2. **Lỗi logic thẩm định `CameraLooksValid()` dẫn đến Thảm họa Quét Lặp Vô Tận (Detection Thrashing Loop)**:
   - Trong `VisualModsEngine::CameraLooksValid(uintptr_t moduleBase, size_t moduleSize)`:
     Hàm đánh giá tính hợp lệ dựa trên thang điểm của `LooksLikeCameraFields(...)`.
   - Tuy nhiên, tại `src/visual_tool/main.cpp`:
     ```cpp
     const bool camInvalid = engine->CameraAddress() != 0 && !engine->CameraLooksValid();
     ```
     Hàm được gọi với tham số mặc định `moduleBase = 0, moduleSize = 0`. Điều này làm biểu thức:
     ```cpp
     const bool vtableOk = moduleBase != 0 && moduleSize != 0 && vtable >= moduleBase && vtable < moduleBase + moduleSize;
     ```
     luôn trả về `false` $\rightarrow$ mất trắng $3\text{ điểm}$ uy tín.
   - Thêm vào đó, tiêu chí `typical` trong mã nguồn:
     ```cpp
     const bool typical = (maxD >= 12.0f && maxD <= 120.0f && cur >= 6.0f && cur <= 250.0f);
     ```
     khóa cứng chặn trên `maxD <= 120.0f`. Khi người chơi zoom vượt quá $2.67\times$ (với `m_origMaxDistance = 45.0f`, $45.0 \times 2.67 = 120.15\text{f}$), `maxD` được nâng lên các giá trị như $135.0\text{f}$ ($3.0\times$) hay $180.0\text{f}$ ($4.0\times$).
   - Kết quả: `typical` lập tức trở thành `false` $\rightarrow$ mất tiếp $3\text{ điểm}$.
   - Lúc này, điểm số tối đa của đối tượng chỉ còn `minOk (+2)` + `fovOk (+2)` = $4\text{ điểm} < 5\text{ điểm}$ (ngưỡng tối thiểu để được công nhận là camera).
   - Ngay lập tức, `CameraLooksValid()` trả về `false` $\rightarrow$ `camInvalid = true` $\rightarrow$ Visual Tool in log `[VisualTool] Camera heap khong con hop le — AutoDetect lai.` và thực thi `engine->SetCameraAddress(0)`.
   - Sau đó tool rơi vào chu trình hủy địa chỉ $\rightarrow$ AutoDetect lại $\rightarrow$ gắn lại $\rightarrow$ ghi trần $> 120.0\text{f}$ $\rightarrow$ lại đánh giá invalid $\rightarrow$ hủy địa chỉ... Vòng lặp thảm họa này phá hủy hoàn toàn trải nghiệm, khiến camera giật loạn xạ và tiêu tốn tài nguyên quét RAM không cần thiết.

---

### 9.2. Đặc tả Kiến trúc Code Patching Chuẩn Thương Mại (Commercial-Grade Engine)

Để loại bỏ hoàn toàn hiện tượng rubberbanding và đạt tới độ mượt mà tuyệt đối ($144\text{ FPS}$ native smooth zoom như PoESmoother hay ExileApi), hệ thống chuyển đổi từ cơ chế ghi Heap bị động sang **Kiến trúc Vá Mã Máy Trực Tiếp trong phân vùng `.text` (Native Code Patching)**.

#### 1. Cấu trúc Quản lý Bản vá `CodePatchRecord`
Mọi điểm can thiệp vào phân vùng thực thi `.text` bắt buộc phải được đóng gói trong cấu trúc tường minh, lưu vết chính xác byte gốc và byte đã vá để phục vụ kiểm toán bộ nhớ và hoàn nguyên tức thì:

```cpp
struct CodePatchRecord {
    std::string name;                   // Định danh bản vá (ví dụ: "CameraClampBypass_cmp2F")
    uintptr_t address = 0;              // Địa chỉ ảo tuyệt đối (VA) trong phân vùng .text
    std::vector<uint8_t> originalBytes; // Byte máy nguyên bản trích xuất từ binary game
    std::vector<uint8_t> patchedBytes;  // Byte máy sau can thiệp (NOPs, JMP, opcode mới)
    bool isPatched = false;             // Cờ theo dõi trạng thái áp dụng

    bool IsValid() const {
        return address != 0 && !originalBytes.empty() && originalBytes.size() == patchedBytes.size();
    }
};
```

#### 2. Cơ chế Quét Mẫu AOB & Ma Trận Dự Phòng (Multi-AOB Fallback Matrix)
Trong phân vùng `.text` của `PathOfExile.exe` (x64), đoạn mã điều khiển clamp khoảng cách camera được nhận diện thông qua hệ thống ma trận mẫu float clamp POE2 (đã bãi bỏ hoàn toàn mẫu POE1 legacy `80 78 2F 00` để chống crash `0xC0000005` tại offset `+0x02BB83D2`):

1. **Mẫu chính (POE2 Direct Float Clamp)**: `F3 0F 10 40 34 0F 2F` (movss xmm, [rax+34h]; comiss).
2. **Mẫu dự phòng thanh ghi (Register Wildcard)**: `F3 0F 10 ? 34 0F 2F` (movss xmm, [reg+34h]; comiss).
3. **Cơ chế Khóa Chặn Mẫu Legacy**: Nếu phát hiện mẫu POE1 `80 78 2F 00` hoặc biến thể thanh ghi, hệ thống lập tức **từ chối vá 4 NOPs** và chuyển an toàn sang **Safe Mode (Data-Only Heap Mode)**.

#### 2.1. Cơ Chế Thẩm Định Ngữ Cảnh Trước Khi Vá (Pre-Patch Context Sanity Verification)
- **Tuyệt đối không vá mù (Anti-Blind Patching)**: Để loại trừ nguy cơ vá nhầm vào hàm khác khi một chuỗi byte ngắn vô tình trùng lặp, hệ thống đọc cửa sổ 64 byte hai chiều `[-32 byte, +32 byte]` xung quanh vị trí match.
- Bắt buộc phải xác nhận có sự hiện diện của các opcode float camera chuyên biệt: `0x0F 0x2F` (`comiss`) hoặc `0xF3 0x0F` (`movss`).
- Tuyệt đối từ chối các mẫu không có ngữ cảnh camera hoặc mẫu POE1 legacy `80 78 2F 00` để triệt tiêu hoàn toàn nguy cơ crash `0xC0000005` tại `+0x02BB83D2`.

#### 2.2. Cơ Chế Tự Động Hạ Cấp Xuống Data-Only Heap Mode (Graceful Degradation / Safe Mode)
- Nếu game update bản vá làm thay đổi hoàn toàn cấu trúc ASM, hoặc toàn bộ AOB trong `.text` đều thất bại, hoặc phát hiện mẫu POE1 cũ:
  * Hệ thống KHÔNG bao giờ bị crash hay dừng hoạt động.
  * Tự động chuyển xuống tầng dự phòng an toàn: **Data-Only Heap Mode (Safe Mode)** (`WriteMaxAndZFar` trên `CameraInstance`).
  * Người chơi vẫn điều khiển được góc nhìn qua wheel-drive và maxDistance trên Heap mà không gặp bất kỳ nguy cơ nào.

Đoạn mã điều khiển clamp khoảng cách camera POE2 chuẩn hóa:

```text
Mẫu AOB Chính (POE2 Float Clamp):
  F3 0F 10 40 34           ; movss xmm0, dword ptr [rax+34h]  (maxDistance)
  0F 2F C8                 ; comiss xmm1, xmm0                (clamp so sánh)
```

- **Chiến lược Vá Mã (Patch Strategies)**:
  1. **Bypass Nhánh Rẽ Điều Kiện (Branch Redirection)**: Thay thế lệnh nhảy có điều kiện (`74 xx` [JZ]) thành lệnh nhảy không điều kiện (`EB xx` [JMP]), hoặc NOPs (`90 90`), ép Game Engine luôn bỏ qua nhánh kẹp khoảng cách.
  2. **Vô hiệu hóa So sánh Kẹp (Clamp NOPing)**: Thay thế cụm lệnh `comiss xmm1, xmm0` (3 bytes: `0F 2F C8`) bằng 3 byte NOP (`90 90 90`). Game Engine sẽ cho phép `currentDistance` nội suy trơn tru theo con lăn chuột đến bất kỳ khoảng cách nào mà không bị gò bó bởi trần cũ.

Khi mã máy trong `.text` đã được vá, Render Loop $144\text{ FPS}$ của chính POE2 sẽ tính toán và hiển thị camera một cách tự nhiên. Hiện tượng giật cục biến mất hoàn toàn ($0\text{ms}$ lag, $0$ frame rách).

#### 3. Quy trình Can thiệp Bộ nhớ An toàn (Safe VirtualProtectEx Protocol)
Do phân vùng `.text` có thuộc tính bảo vệ mặc định là `PAGE_EXECUTE_READ` (RX), mọi thao tác can thiệp trực tiếp mà không chuyển đổi quyền hạn đều sẽ kích hoạt biệt lệ `STATUS_ACCESS_VIOLATION` (`0xC0000005`). Hệ thống tuân thủ nghiêm ngặt quy trình 5 bước:

```mermaid
sequenceDiagram
    participant VT as VisualModsEngine
    participant OS as Windows Kernel (VirtualProtectEx)
    participant CPU as CPU Instruction Cache (L1i)
    participant GameText as POE2 .text Segment

    VT->>GameText: 1. Đọc và sao lưu originalBytes (Safe Verification)
    VT->>OS: 2. VirtualProtectEx(address, size, PAGE_EXECUTE_READWRITE, &oldProtect)
    OS-->>VT: Trả về oldProtect = PAGE_EXECUTE_READ
    VT->>GameText: 3. WriteProcessMemory(address, patchedBytes)
    VT->>OS: 4. VirtualProtectEx(address, size, oldProtect, &temp) (Khôi phục quyền RX)
    VT->>CPU: 5. FlushInstructionCache(hProcess, address, size)
    Note over CPU,GameText: Pipeline thực thi lệnh nhận mã mới an toàn tuyệt đối
```

- **Lưu ý Bắt buộc**: Sau khi khôi phục quyền bảo vệ ban đầu, **bắt buộc phải gọi `FlushInstructionCache()`**. Nếu thiếu lời gọi này, CPU x86-64 đa lõi có thể tiếp tục thực thi các chỉ lệnh cũ từ bộ nhớ đệm L1i (Instruction Cache), gây ra hành vi không đồng nhất hoặc crash tiến trình.

#### 4. Hoàn nguyên Sạch 100% trong 1 Frame (1-Frame Clean Panic Restore)
- **Bất biến An toàn Tuyệt đối**: Khi xảy ra sự kiện Panic Hotkey (`Pause/Break`, `Ctrl+Shift+F12`), người dùng tắt Visual Tool, hoặc khi tiến trình bị đóng (nhận tín hiệu `SIGINT`, `CTRL_CLOSE_EVENT`, `WM_DESTROY`):
- `PanicRestoreAll()` duyệt tức thì qua danh sách `std::vector<CodePatchRecord>`:
  - Nếu `record.isPatched == true`:
    1. Chuyển quyền sang `PAGE_EXECUTE_READWRITE`.
    2. Ghi trả chính xác `record.originalBytes`.
    3. Trả lại thuộc tính `oldProtect`.
    4. Gọi `FlushInstructionCache()`.
    5. Đặt lại `record.isPatched = false`.
- Toàn bộ thời gian hoàn nguyên diễn ra trong thời gian cực ngắn $< 0.1\text{ ms}$ (đúng 1 frame duy nhất), đưa binary game về trạng thái nguyên bản $100\%$, bảo đảm không để lại bất kỳ dấu vết can thiệp nào trước các cơ chế quét bộ nhớ (Zero In-Memory Artifacts).

---

### 9.3. Tinh chỉnh `CameraLooksValid()` & Quản trị Vòng đời Heap Camera

Để triệt tiêu vĩnh viễn thảm họa Detection Thrashing Loop, hai cải tiến kỹ thuật bắt buộc phải được áp dụng:

1. **Thừa nhận Khoảng cách Zoom Nới Rộng (`[12.0f, maxHi]`) trong `LooksLikeCameraFields`**:
   - Khi tính năng nới trần (`unlockedKeepAlive` hoặc `m_zoomActive`) đang hoạt động, giới hạn trên của `maxD` trong `typical` phải được nới lỏng tương ứng với hệ số zoom trần:
     $$\text{maxHi} = \text{kVisualZoomMax} \times 45.0\text{f} = 8.0 \times 45.0\text{f} = 360.0\text{f} \quad (\text{ngưỡng an toàn: } 400.0\text{f})$$
   - Biểu thức thẩm định mới:
     ```cpp
     const float maxAllowedDistance = (unlockedKeepAlive || m_zoomActive) ? 400.0f : 120.0f;
     const bool typical = (maxD >= 12.0f && maxD <= maxAllowedDistance && cur >= 6.0f && cur <= 500.0f);
     ```
   - Nhờ đó, khi zoom lên $3.0\times$ hay $4.0\times$, `typical` vẫn giữ nguyên điểm uy tín ($+3\text{ điểm}$), không bao giờ bị đánh tụt điểm giả tạo.

2. **Lưu Giữ Nội Tại `m_moduleBase` & `m_moduleSize` trong `VisualModsEngine`**:
   - Để khắc phục triệt để lỗi gọi thiếu tham số từ bên ngoài (`CameraLooksValid()` gọi mặc định `moduleBase=0, moduleSize=0`), lớp `VisualModsEngine` được trang bị hai thuộc tính thành viên nội tại:
     ```cpp
     uintptr_t m_moduleBase = 0;
     size_t m_moduleSize = 0;
     ```
   - Khi phương thức `AutoDetect(pid, store)` tìm thấy module chính thông qua `ResolveMainModule(pid, m_reader, mainMod)`, engine tự động lưu trữ phạm vi địa chỉ:
     ```cpp
     m_moduleBase = mainMod.base;
     m_moduleSize = mainMod.size;
     ```
   - Cung cấp API quản lý phạm vi module:
     ```cpp
     void SetModuleBounds(uintptr_t base, size_t size) { m_moduleBase = base; m_moduleSize = size; }
     uintptr_t ModuleBase() const { return m_moduleBase; }
     size_t ModuleSize() const { return m_moduleSize; }
     ```
   - Trong `CameraLooksValid(uintptr_t moduleBase, size_t moduleSize) const`:
     ```cpp
     const uintptr_t modBase = (moduleBase != 0) ? moduleBase : m_moduleBase;
     const size_t modSize = (moduleSize != 0) ? moduleSize : m_moduleSize;
     return LooksLikeCameraFields(vtable, cur, minD, maxD, fov, zNear, zFar, modBase, modSize, true);
     ```
   - **Tác động kỹ thuật**:
     * Kích hoạt liên tục điểm thưởng `vtableOk (+3)`, nâng tổng điểm tin cậy của camera hợp lệ lên $8 - 12\text{ điểm}$ (vượt xa ngưỡng yêu cầu $5\text{ điểm}$).
     * Loại bỏ hoàn toàn việc nhận nhầm các buffer vertex/mesh/chuỗi rác trên Heap (vì `vtable` rác không bao giờ nằm trong khoảng `[modBase, modBase + modSize]`).
     * Chấm dứt triệt để thảm họa tự hủy địa chỉ camera khi chuyển map (Zone Transition), giữ góc nhìn ổn định vững chắc.

3. **Chống Reset Vội Vã khi Người Chơi Đang Tương Tác (Input Interlock)**:
   - Nghiêm cấm việc gọi `SetCameraAddress(0)` chỉ vì một frame đọc sai lệch tức thời.
   - Chỉ được coi là camera mất hợp lệ khi `ReadProcessMemory` đọc thất bại liên tiếp trong $\ge 10\text{ ticks}$ ($500\text{ ms}$) hoặc con trỏ `vtable` trỏ sang địa chỉ không thể truy cập (unmapped page). Điều này giữ cho góc nhìn camera luôn ổn định vững chắc trong suốt quá trình chơi game.

---

### 9.4. Triệt Tiêu Khóa Nhầm Buffer Rác & Báo "Camera OK" Ảo (Root Cause & Anti-False-Positive Gate)

- **Nguyên nhân gốc rễ (RCA)**:
  1. Trong phiên bản POE2 v0.5.5, một số vùng nhớ Heap (như buffer vertex, UI mesh, hoặc chuỗi ký tự ASCII định dạng font) chứa các chuỗi float lặp lại xấp xỉ `~14.5f` đến `~15.3f`.
  2. Thuật toán `LooksLikeCameraFields` cũ không bắt buộc `vtable` phải nằm trong phân vùng thực thi `.rdata` của module game, và cho phép `fov` rơi vào dải `5.0f .. 90.0f` (nhầm lẫn giữa độ góc và radian).
  3. Kết quả: Tool khóa nhầm một buffer vertex rác (`0x159f113c688`), ghi đè `118.09f` vào đó và báo "Camera OK" kèm "Zoom 7.8x/8.0x" trên thanh overlay, nhưng thực tế in-game camera không hề dịch chuyển.
- **Biện pháp khắc phục triệt để**:
  1. **Khóa chặn Vtable nghiêm ngặt**: Khi `moduleBase != 0 && moduleSize != 0`, con trỏ `vtable` **BẮT BUỘC** phải nằm trong dải `[moduleBase, moduleBase + moduleSize]`. Bất kỳ con trỏ ngoài dải (như `0xaaaaaaac`) đều bị loại bỏ ngay lập tức.
  2. **Yêu cầu độ chênh lệch zoom tối thiểu**: Bắt buộc `(maxD - minD) >= 5.0f` để loại trừ hoàn toàn các buffer có các giá trị float gần như bằng nhau.

---

### 9.5. Hệ Thống Giám Sát Quang Học Thời Gian Thực (Closed-Loop Optical Visual Watcher)

Theo **Rule 13** (Closed-Loop Autonomous Repair Protocol):
- Xây dựng module `src/assistant_tool/visual_watcher.py`:
  1. **Chụp ảnh màn hình thật (Ground Truth Verification)**: Kết nối trực tiếp vào Desktop tương tác (`user32.OpenDesktopW("Default")`), chụp ảnh toàn cảnh cửa sổ game POE2.
  2. **Đối soát hai tầng (Memory IPC vs Visual Reality)**:
     - Đọc trạng thái Shared Memory `AutoPOE2_VisualTool_v1`.
     - Nếu Tool báo `Camera OK` và `Zoom >= 1.5x`, nhưng ảnh chụp thực tế cho thấy góc nhìn vẫn ở cự ly gốc $1.0\times$ $\rightarrow$ lập tức kích hoạt sự kiện **`OPTICAL_ZOOM_DESYNC_ANOMALY`**.
  3. **Đóng gói Hồ sơ Sự cố (Incident Dossier)**:
     - Tự động xuất ảnh gắn nhãn `DESYNC_CORRELATED.png` và tệp `incident_dossier.json` vào thư mục `debug_harness/incidents/INCIDENT_<TIMESTAMP>_OPTICAL_ZOOM_DESYNC/`.
     - Cho phép phát hiện và tự động giải phóng con trỏ camera giả mạo mà không cần người dùng phải phán đoán thủ công.

---

## 10. Đặc Tả Kiến Trúc 2 Tầng Hiển Thị In-Game Transparent Overlay & Cơ Chế Thread-Safe Logging (`SetOverlayLog`)

### 10.1. Nâng Cấp Kích Thước & Thuộc Tính Cửa Sổ Lớp Phủ (760x70 px Overlay Window Specs)

Để hiển thị trọn vẹn cả thông số vận hành cốt lõi và nhật ký chẩn đoán chi tiết theo thời gian thực mà không bị co cụm hay tràn màn hình, kích thước cửa sổ lớp phủ được nâng cấp chuẩn hóa từ `620x52 px` lên **`760x70 px`**:

```text
+---------------------------------------------------------------------------------------+
|  AutoPOE2 Visual Tool In-Game Transparent Overlay (760 x 70 px)                       |
|  WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE | WS_EX_TOOLWND |
+---------------------------------------------------------------------------------------+
| Dòng 1 (Y: 4..36px):  Visual Tool  |  Zoom 2.4x / trần 4.0x  |  Fog OFF  |  Camera OK  |
+---------------------------------------------------------------------------------------+
| Dòng 2 (Y: 36..66px): [PID 11644] Cam: 0x2880c5e2180 (Safe Mode) | Wheel: +0.20x     |
+---------------------------------------------------------------------------------------+
```

- **Các thuộc tính mở rộng bắt buộc (Extended Window Styles)**:
  * `WS_EX_LAYERED`: Bật khả năng vẽ alpha blending (`SetLayeredWindowAttributes(hwnd, 0, 220, LWA_ALPHA)` đạt độ trong suốt ~86%, hiển thị nền kính tối `RGB(15, 23, 42)` cực kỳ tinh tế).
  * `WS_EX_TRANSPARENT`: Cửa sổ xuyên thấu click chuột $100\%$ (Mouse Click-Through) — toàn bộ thao tác click chuột trái/phải/giữa xuyên thẳng vào game client POE2 bên dưới, tuyệt đối không cản trở thao tác chiến đấu.
  * `WS_EX_NOACTIVATE`: Không bao giờ cướp tiêu điểm (Focus Stealing) khi hiển thị hoặc gọi `SetWindowPos`.
  * `WS_EX_TOPMOST`: Luôn duy trì trên cùng cửa sổ game `PathOfExile.exe`.
  * `WS_EX_TOOLWINDOW`: Ẩn hoàn toàn khỏi thanh Taskbar và danh sách chuyển ứng dụng Alt+Tab.
- **Thuật toán Tự Động Định Vị (Auto-Centering Algorithm)**:
  ```cpp
  constexpr int kOverlayW = 760;
  constexpr int kOverlayH = 70;

  void PositionOverlay() {
      if (!g_overlayHwnd) return;
      HWND poe = FindPoe2Window();
      RECT wr{};
      if (poe && GetWindowRect(poe, &wr)) {
          const int x = wr.left + ((wr.right - wr.left) - kOverlayW) / 2;
          const int y = wr.top + 8;
          SetWindowPos(g_overlayHwnd, HWND_TOPMOST, x, y, kOverlayW, kOverlayH, SWP_NOACTIVATE);
      } else {
          SetWindowPos(g_overlayHwnd, HWND_TOPMOST, 40, 12, kOverlayW, kOverlayH, SWP_NOACTIVATE);
      }
  }
  ```

---

### 10.2. Bố Cục Hiển Thị 2 Dòng Phân Tách (Dual-Line Layout Architecture)

Hệ thống phân chia diện tích hiển thị $760 \times 70\text{ px}$ thành hai khu vực trực quan:

1. **Dòng 1: Thông Số Vận Hành Cốt Lõi (Primary Metrics - `rcLine1 = {0, 4, 760, 36}`)**:
   - **Font chữ**: Segoe UI Semibold, kích thước `-16px` (~12pt), chất lượng ClearType (`CLEARTYPE_QUALITY`).
   - **Màu sắc**: Trắng xám sáng `RGB(226, 232, 240)` (Tailwind Slate-200).
   - **Định dạng**:
     $$\text{Visual Tool} \quad|\quad \text{Zoom } \{\text{cur}\}\text{x} \text{ / trần } \{\text{cap}\}\text{x} \quad|\quad \text{Fog } \{\text{OFF/ON}\} \quad|\quad \{\text{Camera State}\}$$
   - **Bảng trạng thái Camera**:
     * `Chờ PathOfExile.exe`: Chưa phát hiện hoặc chưa gắn được vào PID game.
     * `Đã gắn — chưa thấy camera (vào map)`: Đã gắn tiến trình nhưng nhân vật đang ở Login/Lobby hoặc đang load instance.
     * `Camera OK (Heap / Safe Mode)`: Đã khóa chính xác cấu trúc Camera Instance trong map.

2. **Dòng 2: Nhật Ký Chẩn Đoán & Hoạt Động Thời Gian Thực (Realtime Diagnostic & Activity Log - `rcLine2 = {0, 36, 760, 66}`)**:
   - **Font chữ**: Consolas / Segoe UI Regular, kích thước `-13px` (~9.5pt), căn giữa màn hình.
   - **Màu sắc**: Xanh Cyan nổi bật `RGB(56, 189, 248)` (Tailwind Sky-400) hoặc Slate-400 `RGB(148, 163, 184)`.
   - **Chức năng**: Ghi nhận trực tiếp từng sự kiện hoạt động của hệ thống để người dùng kiểm chứng và phản hồi ngay trên màn hình:
     * **PID & Địa chỉ Camera**: `[PID 11644] Cam: 0x2880c5e2180 (Safe Mode Heap) | Sẵn sàng`
     * **Sự kiện Cuộn chuột (Wheel Delta)**: `[WHEEL] Delta: +0.20x -> cur=2.40x / cap=4.00x | WPM OK`
     * **Sự kiện Phím Tắt Khẩn Cấp (Panic Killswitch)**: `[PANIC] Phím Pause/Break -> Hoàn nguyên sạch 100% về mặc định gốc`
     * **Sự kiện Chuyển Map (Zone Transition)**: `[MAP TRANSITION] Camera heap cũ giải phóng -> Đang AutoDetect lại...`
     * **Cảnh báo Bất thường (Anomaly Alert)**: `[WARN] Camera vtable lệch module bounds (streak 3/10) -> Đang kiểm tra...`

---

### 10.3. Cơ Chế Giao Tiếp Đa Luồng Phi Chặn `SetOverlayLog` (Thread-Safe Non-Blocking Logging)

- **Thách thức kiến trúc đa luồng**:
  * Luồng `MouseHookThread` (TID dedicated, bắt sự kiện `WM_MOUSEWHEEL` ở chu kỳ cực nhanh $< 0.1\text{ms}$).
  * Luồng `EngineLoop` (Worker thread chu kỳ 50ms, thực hiện RPM/WPM, kiểm tra vtable và duy trì trần).
  * Luồng `OverlayProc` (Main thread, nhận `WM_TIMER` 50ms và `WM_PAINT` để vẽ GDI lên màn hình).
  * Nếu ghi trực tiếp vào chuỗi dùng chung mà không có cơ chế đồng bộ, sẽ gây ra **Data Race**, phá vỡ bộ nhớ đệm chuỗi và kích hoạt Fast Fail `0xC0000409` trong thư viện `ucrtbase.dll`.
- **Đặc tả hiện thực Thread-Safe `SetOverlayLog`**:
  ```cpp
  namespace {

  std::mutex g_logMutex;
  std::wstring g_overlayLogText = L"[VisualTool] Đang khởi tạo...";

  void SetOverlayLog(const std::wstring& msg) {
      std::lock_guard<std::mutex> lock(g_logMutex);
      g_overlayLogText = msg;
  }

  std::wstring GetOverlayLog() {
      std::lock_guard<std::mutex> lock(g_logMutex);
      return g_overlayLogText;
  }

  } // namespace
  ```
- **Quy trình vẽ GDI an toàn tuyệt đối trong `DrawOverlay()`**:
  ```cpp
  void DrawOverlay() {
      if (!g_overlayHwnd || !g_ipc) return;
      HDC hdc = GetDC(g_overlayHwnd);
      RECT rc{};
      GetClientRect(g_overlayHwnd, &rc);

      // 1. Vẽ nền kính tối bán trong suốt (Slate-900)
      HBRUSH bg = CreateSolidBrush(RGB(15, 23, 42));
      FillRect(hdc, &rc, bg);
      DeleteObject(bg);
      SetBkMode(hdc, TRANSPARENT);

      // 2. Vẽ Dòng 1: Primary Metrics
      RECT rcLine1 = { rc.left, rc.top + 4, rc.right, rc.top + 36 };
      SetTextColor(hdc, RGB(226, 232, 240));
      HFONT fontLine1 = CreateFontW(-16, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0, DEFAULT_CHARSET,
          OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"Segoe UI");
      HFONT oldFont = static_cast<HFONT>(SelectObject(hdc, fontLine1));

      const float cap = (std::isfinite(g_ipc->zoom_cap) && g_ipc->zoom_cap >= 1.0f) ? g_ipc->zoom_cap : 4.0f;
      const float cur = (std::isfinite(g_ipc->current_zoom) && g_ipc->current_zoom >= 1.0f) ? g_ipc->current_zoom : 1.0f;
      const bool fogOff = (g_ipc->flags & kVisualFlagDisableFog) != 0;
      const bool attached = (g_ipc->status & kVisualStatusAttached) != 0;
      const bool camOk = (g_ipc->status & kVisualStatusCameraOk) != 0;

      const wchar_t* state = L"Chờ PathOfExile.exe";
      if (attached && camOk) {
          state = L"Camera OK";
      } else if (attached) {
          state = L"Đã gắn — chưa thấy camera (vào map)";
      }

      std::wostringstream woss1;
      woss1 << std::fixed << std::setprecision(1);
      woss1 << L"Visual Tool  |  Zoom " << cur << L"x / trần " << cap << L"x  |  Fog "
            << (fogOff ? L"OFF" : L"ON") << L"  |  " << state;
      const std::wstring line1 = woss1.str();
      DrawTextW(hdc, line1.c_str(), static_cast<int>(line1.length()), &rcLine1, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

      // 3. Vẽ Dòng 2: Realtime Diagnostic & Activity Log
      RECT rcLine2 = { rc.left, rc.top + 36, rc.right, rc.bottom - 4 };
      SetTextColor(hdc, RGB(56, 189, 248)); // Tailwind Sky-400
      HFONT fontLine2 = CreateFontW(-13, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET,
          OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH, L"Segoe UI");
      SelectObject(hdc, fontLine2);

      const std::wstring line2 = GetOverlayLog();
      DrawTextW(hdc, line2.c_str(), static_cast<int>(line2.length()), &rcLine2, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

      // 4. Giải phóng tài nguyên GDI sạch sẽ
      SelectObject(hdc, oldFont);
      DeleteObject(fontLine1);
      DeleteObject(fontLine2);
      ReleaseDC(g_overlayHwnd, hdc);
  }
  ```

---

## 11. Bảng Ma Trận Đối Soát Kiến Trúc & Bằng Chứng Khóa Chặn (Zero-Drift Compliance Matrix)

| Hạng Mục Kiến Trúc | Đặc Tả Kỹ Thuật (Specification) | Hiện Trạng Mã Nguồn (Code Alignment) | Trạng Thái Tuân Thủ |
| :--- | :--- | :--- | :--- |
| **Kích thước Overlay** | `760 x 70 px`, căn giữa cửa sổ POE2 | `kOverlayW = 760; kOverlayH = 70;` trong `src/visual_tool/main.cpp` | **ĐỒNG BỘ 100%** |
| **Kiến trúc Hiển thị** | Dual-Line (Dòng 1: Metrics, Dòng 2: Realtime Log) | `DrawOverlay()` vẽ tách biệt `rcLine1` và `rcLine2` | **ĐỒNG BỘ 100%** |
| **Giao tiếp Đa luồng** | Thread-Safe non-blocking qua `SetOverlayLog` | `g_logMutex` bảo vệ `g_overlayLogText`, copy nhanh $< 0.1\mu\text{s}$ | **ĐỒNG BỘ 100%** |
| **Phạm vi Module** | Caching `m_moduleBase` & `m_moduleSize` trong Engine | `VisualModsEngine` lưu và tự động dùng trong `CameraLooksValid()` | **ĐỒNG BỘ 100%** |
| **Điều phối Zoom** | `maxDistance` mở khóa tới `zoomCap`, không kẹp theo cur | `StepZoom` và `Maintain` decoupled độc lập hoàn toàn | **ĐỒNG BỘ 100%** |
| **Phòng vệ Anti-Crash** | Triệt tiêu hoàn toàn mã lỗi `0xC0000409` & `0xC0000005` | Sanitize `isfinite`, bãi bỏ patch `.text` POE1, dùng Safe Mode | **ĐỒNG BỘ 100%** |



