# 10. ĐẶC TẢ KIẾN TRÚC PHÁT HIỆN TIẾN TRÌNH GAME, VÒNG ĐỜI TỰ HÀNH & GIAO THỨC PHÍM NÓNG AN TOÀN

- **Mã Tài Liệu**: `SPEC-DOC-10-CLIENT-LIFECYCLE-HOTKEY-SAFETY-2026`
- **Mốc Thời Gian Chuẩn Hóa**: `10/09/2026 - 18:50:00`
- **Phiên Bản Áp Dụng**: Path of Exile 2 (v0.5.5 / Early Access Q3-2026)
- **Tiêu Chuẩn Thực Hiện**: Windows Win32 SDK, C++23 ISO, Python 3.11 Threading & Subprocess, Zero Documentation Drift (Điều 6 AGENTS.md / GEMINI.md).
- **Trạng Thái Kỹ Thuật**: [ĐÃ PHÊ DUYỆT & ĐỒNG BỘ MÃ NGUỒN 100%]

---

## 1. TỔNG QUAN & BỐI CẢNH BÀI TOÁN

Trong các đợt chạy thử nghiệm trên máy thật với hệ điều hành Windows 10/11 x64, hệ thống AutoPOE2 ghi nhận lỗi gián đoạn nghiêm trọng tại tầng điều phối giao diện:
> **Mô tả hiện tượng**: Người dùng nhấn nút **"▶ BẬT TỰ HÀNH [F8 / F6]"** hoặc **"▶ BẮT ĐẦU BOT"** trên Control Center GUI, nút bấm lập tức rơi vào trạng thái nhấp nháy liên tục (flickering) giữa màu XANH (Bắt đầu) và màu ĐỎ (Dừng) mỗi ~1.0 giây. Game không thể tự động mở, hoặc nếu game đang mở thì Core Engine bị tắt và khởi động lại liên tục, tiêu tốn CPU và khiến hệ thống rơi vào trạng thái tê liệt.

Tài liệu này đặc tả toàn diện nguyên nhân gốc rễ ở cấp hệ điều hành, phân tích luồng tương tác giữa C++ Core Launcher, Python Companion, Windows Win32 API, và thiết lập các chuẩn mực kiến trúc bảo vệ hệ thống không bao giờ gặp lại lỗi này.

---

## 2. PHÂN TÍCH KỸ THUẬT BỐN NGUYÊN NHÂN GỐC RỄ (ROOT CAUSE ANALYSIS)

```mermaid
graph TD
    subgraph "Root Causes Chain"
        RC1["1. Phát hiện Game thiếu hụt<br/>Chỉ tìm POEC2_MainWindow, thiếu POEWindowClass & Toolhelp32"]
        RC2["2. CreateProcessW lỗi chuỗi lệnh<br/>lpApplicationName=NULL bọc ngoặc kép -> Error 2"]
        RC3["3. Vòng lặp Hủy diệt Vô tận<br/>Lifecycle 1s gọi start() -> core_controller taskkill /F chính nó"]
        RC4["4. Phím nóng ma GetAsyncKeyState<br/>Check LSB (state & 1) gây kích hoạt giả + thiếu Mutex"]
    end
    
    RC1 -->|Game báo False khi đang load| RC3
    RC2 -->|Game không mở được| RC3
    RC3 -->|State đảo True/False mỗi 1s| Flicker["NÚT START NHẤP NHÁY XANH/ĐỎ LIÊN TỤC"]
    RC4 -->|Spam click / Ghost trigger| RC3
```

### 2.1. Nguyên nhân 1: Nhận diện Cửa sổ & Tiến trình Game Thiếu Sót
- **Hiện trạng mã nguồn cũ**: Cả `IsPoe2Running()` trong `src/launcher/main.cpp` và `is_client_running()` trong `src/assistant_tool/client_launcher.py` chỉ kiểm tra:
  ```python
  hwnd = ctypes.windll.user32.FindWindowW("POEC2_MainWindow", None)
  if not hwnd:
      hwnd = ctypes.windll.user32.FindWindowW(None, "Path of Exile 2")
  ```
- **Lỗ hổng kỹ thuật**:
  1. *Thiếu Window Class chuẩn*: Grinding Gear Games sử dụng lớp cửa sổ chuẩn là `POEWindowClass` cho engine Path of Exile. Một số bản thử nghiệm nội bộ hoặc build mới dùng `POEC2_MainWindow`, nhưng phần lớn môi trường người dùng chạy dưới `POEWindowClass`.
  2. *Độ trễ khởi tạo GUI của Game*: API `FindWindowW` chỉ trả về hợp lệ khi cửa sổ đồ họa được tạo và đăng ký với Desktop Window Manager (DWM). Trong suốt quá trình game nạp tài nguyên ban đầu (Splash screen, Vulkan/DirectX 12 pipeline initialization, kiểm tra file `Content.ggpk` kéo dài từ 5 đến 20 giây), cửa sổ game chưa xuất hiện khiến `FindWindowW` luôn trả về `NULL`.
  3. *Không quét Process Table cấp OS*: Hoàn toàn không kiểm tra danh sách tiến trình qua Windows Toolhelp32 Snapshot (`CreateToolhelp32Snapshot`). Kết quả là dù tiến trình `PathOfExile.exe` đang chạy và tiêu thụ CPU/RAM, các bộ kiểm tra vẫn kết luận sai rằng game chưa chạy (`False`).

### 2.2. Nguyên nhân 2: Lỗi Phân Tách Tham Số trong `CreateProcessW`
- **Hiện trạng mã nguồn cũ**:
  ```cpp
  std::wstring gameCmd = L"\"" + poeExe.wstring() + L"\"";
  CreateProcessW(NULL, gameCmd.data(), NULL, NULL, FALSE, 0, NULL, gameDir.c_str(), &siGame, &piGame);
  ```
- **Lỗ hổng kỹ thuật**:
  - Khi tham số thứ nhất `lpApplicationName` là `NULL`, Win32 API `CreateProcessW` sử dụng quy tắc phân tách khoảng trắng trên tham số thứ hai `lpCommandLine`.
  - Nếu đường dẫn cài đặt chứa khoảng trắng và ký tự đặc biệt (ví dụ: `C:\Program Files (x86)\Grinding Gear Games\Path of Exile 2\PathOfExile.exe`), việc bọc dấu ngoặc kép không tương thích với buffer sửa đổi (mutable buffer) trong một số ngữ cảnh gọi của Windows dẫn tới mã lỗi hệ thống `2` (`ERROR_FILE_NOT_FOUND`).
  - Hơn nữa, `CreateProcessW` không thể tự nâng quyền UAC (Run as Administrator) nếu client game nằm trong thư mục bảo vệ hệ thống, dẫn tới `WinError 740`.

### 2.3. Nguyên nhân 3: Vòng Lặp Hủy Diệt Vô Tận (Infinite Restart-Kill Loop)
- **Hiện trạng mã nguồn cũ**:
  1. `AutonomousLifecycleManager` chạy luồng ngầm `_lifecycle_loop` với tần suất quét 1.0 giây/lần.
  2. Ở mỗi chu kỳ, hàm `_evaluate_and_act()` kiểm tra `is_client_running()`. Do Nguyên nhân 1, nếu game đang nạp, hàm trả về `False`, khiến nó liên tục kích hoạt `launch_client()`.
  3. Khi game đã có HWND, nó chuyển sang bước 2: `if not self.core_controller.is_running(): self.core_controller.start(self.config)`.
  4. Trong `core_controller.start()` (dòng 187), mã nguồn có lệnh dọn dẹp tiến trình cũ:
     ```python
     self._kill_orphan_core_processes() # Gọi subprocess taskkill /F /IM AutoPOE2_Core.exe
     ```
- **Hậu quả thảm họa**:
  - Cứ sau mỗi 1.0 giây, khi `is_running()` chưa kịp chuyển sang `True` (vì Core Engine C++ cần 1.5 - 2s để nạp các mô-đun bộ nhớ và thiết lập Shared Memory), vòng lặp của `AutonomousLifecycleManager` lại gọi `start()`.
  - Lệnh `start()` lại gọi `taskkill /F /IM AutoPOE2_Core.exe`, **tự tay tiêu diệt ngay lập tức tiến trình C++ Core mà nó vừa mới bật lên ở 1 giây trước**!
  - Khi Core bị giết: `_emit_state(False)` được gửi tới GUI -> Nút chuyển sang màu XANH (Bắt đầu).
  - Vòng lặp 1 giây sau thấy Core đã tắt -> lại gọi `start()` -> phát `_emit_state(True)` -> Nút chuyển sang màu ĐỎ (Dừng) -> rồi lại gọi `taskkill /F` -> lại giết Core!
  - Kết quả: Nút bấm trên giao diện đổi màu liên tục mỗi giây, tiến trình Core bị giết hàng chục lần, Shared Memory bị phân mảnh.

### 2.4. Nguyên nhân 4: Phím Nóng Ma từ Bit LSB của `GetAsyncKeyState`
- **Hiện trạng mã nguồn cũ**:
  ```python
  is_toggle = (state_f8 & 0x8000 or state_f8 & 1) or (state_f6 & 0x8000 or state_f6 & 1)
  ```
- **Lỗ hổng kỹ thuật**:
  - Bit 0 (Least Significant Bit - `state & 1`) của hàm Win32 `GetAsyncKeyState` cho biết phím đã được nhấn kể từ lần gọi hàm trước đó hay không.
  - Tuy nhiên, trong môi trường Windows NT đa luồng và các ứng dụng đồ họa nặng, cờ LSB này rất không ổn định (unreliable). Nó có thể bị set bởi thông điệp phím trễ từ hàng đợi thông điệp của hệ thống, dẫn đến việc kích hoạt lệnh dù người dùng **không hề bấm phím vật lý**.
  - Kết hợp với việc thiếu cờ khóa trạng thái khởi động (`_starting` Mutex) và thiếu debounce timer, các luồng GUI và Hotkey kích hoạt đan xen nhau làm cho vòng lặp Start/Stop bị kích hoạt loạn xạ.

---

## 3. ĐẶC TẢ KIẾN TRÚC GIẢI PHÁP ĐỒNG BỘ (SYSTEM SOLUTIONS SPECIFICATION)

```mermaid
sequenceDiagram
    autonumber
    participant User as Người Dùng / Hotkey
    participant UI as Control Center GUI
    participant Guard as Startup Mutex & Debounce
    participant ALM as AutonomousLifecycleManager
    participant CC as CoreController
    participant OS as Windows OS (Shell & Toolhelp32)

    User->>UI: Bấm nút "▶ BẬT TỰ HÀNH"
    UI->>Guard: Kiểm tra _starting == True?
    alt Đang khởi động (_starting == True)
        Guard-->>UI: Bỏ qua (Ignore duplicate trigger)
    else Chưa khởi động (_starting == False)
        Guard->>Guard: Set _starting = True, Khóa nút GUI (Disable / Badge ⏳)
        UI->>OS: Quét Toolhelp32 & POEWindowClass
        OS-->>UI: Nhận diện chính xác 100% trạng thái Game
        alt Game chưa chạy
            UI->>OS: ShellExecuteW("runas", "PathOfExile.exe")
            Note over OS: Khởi chạy tin cậy với UAC elevation
        end
        UI->>CC: start(config)
        Note over CC: KHÔNG gọi taskkill nếu Core đang sống/khởi động!
        CC->>OS: Popen / ShellExecuteExW AutoPOE2_Core.exe
        CC-->>UI: _emit_state(True)
        UI->>ALM: start(config) kèm Grace Period (5-10s)
        Guard->>Guard: Set _starting = False, Mở khóa nút GUI
    end
```

### 3.1. Cơ Chế Phát Hiện Client Đa Tầng (Multi-Tier Process & Window Detection)
Hệ thống kết hợp kiểm tra 2 tầng độc lập, đảm bảo độ chính xác $100\%$:

#### Tầng 1: Kiểm tra Cửa Sổ Win32 (Window Handle Check)
Hỗ trợ đầy đủ tất cả các lớp và tiêu đề cửa sổ chính thức của Path of Exile 2:
```python
VALID_WINDOW_CLASSES = ["POEWindowClass", "POEC2_MainWindow"]
VALID_WINDOW_TITLES = ["Path of Exile 2", "Path of Exile"]
```
Nếu bất kỳ cặp `(class, title)` nào khớp, hàm xác nhận game đang chạy và có giao diện hiển thị.

#### Tầng 2: Kiểm tra Tiến Trình Hệ Điều Hành (Toolhelp32 Snapshot Check)
Bổ sung cơ chế quét bảng tiến trình hệ thống qua Win32 API:
- **C++ Core / Launcher**:
  ```cpp
  bool IsPoe2ProcessRunning() {
      HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
      if (hSnap == INVALID_HANDLE_VALUE) return false;
      PROCESSENTRY32W pe = { sizeof(pe) };
      if (Process32FirstW(hSnap, &pe)) {
          do {
              if (_wcsicmp(pe.szExeFile, L"PathOfExile.exe") == 0 ||
                  _wcsicmp(pe.szExeFile, L"PathOfExile_x64.exe") == 0 ||
                  _wcsicmp(pe.szExeFile, L"PathOfExileSteam.exe") == 0 ||
                  _wcsicmp(pe.szExeFile, L"PathOfExile2.exe") == 0) {
                  CloseHandle(hSnap);
                  return true;
              }
          } while (Process32NextW(hSnap, &pe));
      }
      CloseHandle(hSnap);
      return false;
  }
  ```
- **Python Companion**: Sử dụng Windows ctypes Toolhelp32 API hoặc `EnumProcesses` để duyệt PID, kiểm tra tên các file thực thi mục tiêu. Chỉ cần tiến trình tồn tại trong kernel, hàm trả về `True` ngay cả khi cửa sổ đồ họa chưa kịp render.

### 3.2. Khởi Chạy Game Tin Cậy Qua Windows Shell (`ShellExecuteW`)
- Trong `src/launcher/main.cpp`:
  ```cpp
  HINSTANCE hInst = ShellExecuteW(
      NULL,
      L"open",
      poeExe.c_str(),
      NULL,
      gameDir.c_str(),
      SW_SHOWNORMAL
  );
  if ((INT_PTR)hInst <= 32) {
      // Fallback thử với quyền Administrator nếu cần
      ShellExecuteW(NULL, L"runas", poeExe.c_str(), NULL, gameDir.c_str(), SW_SHOWNORMAL);
  }
  ```
- **Ưu điểm**: Windows Shell tự động phân giải các khoảng trắng trong đường dẫn `Program Files (x86)`, tự động xử lý thư mục làm việc, và tự động gọi trình quản lý UAC nếu ứng dụng yêu cầu quyền cao hơn. Triệt tiêu hoàn toàn mã lỗi `Error 2`.

### 3.3. Bảo Vệ Tiến Trình Core Engine & Xóa Bỏ Taskkill Tự Hủy
1. **Loại bỏ `_kill_orphan_core_processes()` khỏi luồng `start()`**:
   - `taskkill /F` chỉ được phép kích hoạt trong phương thức `stop()` khi người dùng yêu cầu tắt hệ thống hoặc khi kích hoạt Panic Killswitch (`Pause` / `Ctrl+Shift+F12`).
   - Trong phương thức `start()`: Nếu tiến trình con `self._process` đang hoạt động hoặc handle `self._hProcess` còn hợp lệ, trả về `True` ngay lập tức, không giết tiến trình.
2. **Thiết lập Startup Mutex (`self._starting`)**:
   - Khi một yêu cầu khởi động được gửi tới, biến `self._starting` được bật thành `True`.
   - Bất kỳ yêu cầu `start()` hoặc `stop()` nào đến trong giai đoạn này đều bị từ chối/bỏ qua cho tới khi quá trình khởi động hoàn tất (hoặc quá timeout 5.0s).
3. **Grace Period trong `AutonomousLifecycleManager`**:
   - Sau khi gọi `core_controller.start()`, luồng điều phối tự hành tạm ngưng đánh giá lại trong tối thiểu 3.0 - 5.0 giây (Grace Period) để Core Engine hoàn tất kết nối bộ nhớ chia sẻ và ổn định nhịp đọc 120Hz.

### 3.4. Giao Thức Phím Nóng Toàn Cục Không Ma (Ghost-Free Hotkey Loop)
1. **Loại bỏ hoàn toàn cờ LSB `state & 1`**:
   - Chỉ sử dụng bit `0x8000` (Key is down):
     ```python
     is_f8_down = bool(ctypes.windll.user32.GetAsyncKeyState(VK_F8) & 0x8000)
     is_f6_down = bool(ctypes.windll.user32.GetAsyncKeyState(VK_F6) & 0x8000)
     ```
2. **Debounce Timer Chặt Chẽ**:
   - Đặt ngưỡng tối thiểu giữa 2 lần nhận phím nóng liên tiếp là **0.8 giây**:
     ```python
     if (is_f8_down or is_f6_down) and (now - last_toggle_time > 0.8):
         last_toggle_time = now
         self.after(0, self._on_bot_toggle_hotkey)
     ```
3. **Phản Hồi UI Đồng Bộ**:
   - Ngay khi người dùng bấm Start, nút chuyển ngay sang trạng thái trung gian "⏳ ĐANG KHỞI ĐỘNG..." với màu vàng cam nhạt `#f59e0b`, vô hiệu hóa khả năng click lặp lại cho tới khi nhận sự kiện `_emit_state(True)` từ Core Controller.

### 3.5. Vòng Đời Chuyển Vùng Bản Đồ (Zone Transition Lifecycle) & Khóa An Toàn Tiền Cảnh

#### 3.5.1. Vòng Đời Chuyển Map & Thu Hồi Con Trỏ Cũ (Stale Pointer Invalidation)
Khi người chơi bước qua cổng map, chuyển act hoặc dịch chuyển waypoint:
1. **Bắt sự kiện chuyển vùng thời gian thực**:
   - `LogSensor` lắng nghe tệp `Client.txt` (fallback `LatestClient.txt`), bắt sự kiện `Connecting to instance server...` để xác định thời điểm bắt đầu chuyển map, và `LoadingFinished` / `Entering area` để xác định thời điểm nạp hoàn tất.
2. **Thu hồi con trỏ cũ (Stale Pointer Invalidation)**:
   - Toàn bộ địa chỉ bộ nhớ cũ (`m_playerAddr = 0`, `m_xyzAddr = 0`) được reset về 0 ngay lập tức, ngăn ngừa đọc nhầm vùng heap bị giải phóng do cơ chế ASLR và tái cấp phát của engine POE2.
3. **Phá bế tắc Grace Period & Kích hoạt Siêu Tốc**:
   - Hệ thống phát 1 xung bước chân vi mô (Micro-tap `W` 50ms hoặc click ngắn 60px) để chủ động giải phóng thời gian ân hạn Grace Period hợp lệ.
   - Kích hoạt song song Tầng 1 (Pointer Chain qua `InGameState` 0.001ms) và Tầng 3 (Optical Health Globe Sensor 1-2ms), đưa bot vào trạng thái tuần tra/chiến đấu an toàn chỉ sau **1.2 - 2.0 giây**.
   - *(Chi tiết đặc tả xem tại [**13. Đặc Tả Kiến Trúc Nhận Diện HP Siêu Tốc & Tối Ưu Hóa Chuyển Vùng Bản Đồ**](file:///c:/Projects/AutoPOE2/docs/development/13_fast_hp_detection_and_zone_transition_optimization.md))*.

#### 3.5.2. Giao Thức Khóa An Toàn Phần Cứng & Cửa Sổ Tiền Cảnh (Foreground Hardware Interlock)
Để đảm bảo chuột và bàn phím không bao giờ bị rò rỉ ra màn hình Desktop Windows hay nút Start khi game chưa chạy hoặc khi người dùng chuyển ứng dụng (Alt-Tab):
1. **Tier 1 (Hardware Gateway)**: `KMBoxNet::IsGameWindowFocused()` tự động chặn mọi lệnh UDP và `mouse_event` nếu active window không phải Path of Exile 2 (`POEWindowClass` / `POEC2_MainWindow`).
2. **Tier 2 (Engine Mode)**: Cưỡng chế `allowPhysicalMove = false` khi `gameSession.IsSimulated() == true`.
3. **Tier 3 (Action FSM)**: `EnsurePoe2WindowFocus()` trả về `bool`; đóng băng toàn bộ chu trình FSM nếu mất focus.
4. **Tier 4 (Launcher)**: Cả `AutoPOE2.exe` và `core_controller.py` cấm truyền cờ `--move` khi `IsPoe2Running() == false`.
- Chi tiết đặc tả độc lập: Xem [**`docs/development/11_foreground_hardware_interlock_and_desktop_safety_protocol.md`**](file:///c:/Projects/AutoPOE2/docs/development/11_foreground_hardware_interlock_and_desktop_safety_protocol.md).

#### 3.5.3. Xác Thực Vị Trí Bản Đồ Khi Mở Tool / Core Giữa Map (10/09/2026)

**Hiện trường (18:45 10/09/2026)**: Screenshot `captures/20260910_184541_HUD_CI_VITALS.png` và `Client.txt` PID 36104 xác nhận nhân vật đang ở **Hive Fortress** (`Generating level 77 area "ChayulaLeague_Tower" with seed 3565528473`, `[SCENE] Set Source [Hive Fortress]` lúc 18:29:57). Control Center vẫn treo banner `⏳ [CHỜ DỮ LIỆU GAME] Đang chờ xác thực vị trí bản đồ từ Log Client...`.

**Ba nguyên nhân gốc (không phải thiếu dòng log):**

1. **`Config.find_poe2_client_log()` không tồn tại**: `control_center.py` gọi hàm này trong `try/except` và nuốt exception → LogWatcher Python không bao giờ gắn `C:\Program Files (x86)\Grinding Gear Games\Path of Exile 2\logs\Client.txt`.
2. **Tail bắt đầu ở EOF**: Cả Python `LogWatcher._tail_loop` (`seek END`) và C++ `LogSensor::WorkerLoop` bỏ qua SCENE đã ghi trước khi tool/Core mở. `currentAreaName` / SHM `area_name` rỗng cho tới khi đổi zone lần nữa.
3. **`on_telemetry_snapshot` không set `has_real_map_log`**: ngay cả khi Core đã có tên khu vực chơi thật, lifecycle vẫn coi là chưa xác thực.

**Giải pháp (giữ nguyên cổng an toàn World Map U):**

| Tầng | Hành vi hiện tại |
| :--- | :--- |
| `Config.find_poe2_client_log()` | Trả về `Client.txt` / `LatestClient.txt` thật hoặc `None` — không bịa đường dẫn. |
| `LogWatcher.seed_latest_playable_zone()` | Đọc 2MB đuôi file, lấy `[SCENE] Set Source` chơi thật **cuối cùng**. Bỏ `Act 1–4`, `Atlas`, `(null)`, `[DNT]*`. Gọi **sau** khi `lifecycle_manager` đã sẵn sàng. |
| `LogSensor::ReplayLatestPlayableZoneFromTail()` | Khi mở file: phát `AreaGenerated` + `SceneChanged` + `LoadingFinished` của zone cuối, rồi `offset = EOF`. Overlay `Act 2`/`Atlas` không được phát. |
| `AutonomousLifecycleManager.on_telemetry_snapshot` | `area_name` SHM khác rỗng và không phải UI scene → `has_real_map_log = True`. Chuỗi rỗng / `Act 2` không xác thực. |

**Bất biến giữ nguyên**: `is_in_town` mặc định `False`; Hive Fortress / Sandswept Marsh **không** gửi `send_trigger_world_map_travel`. Chỉ thị trấn/hideout đã xác thực mới được phép phím U.

---

## 4. QUY CHUẨN XÁC MINH & CHỈ SỐ NGHIỆM THU (VERIFICATION METRICS)

| Tiêu Chí Đo Lường | Trạng Thái Cũ | Trạng Thái Sau Chuẩn Hóa | Kết Quả Nghiệm Thu |
| :--- | :--- | :--- | :--- |
| **Phát hiện Client POE2** | Chỉ tìm `POEC2_MainWindow` (Miss Splash) | Quét cả `POEWindowClass` + Toolhelp32 PID | ✅ Đạt 100% nhận diện |
| **Khởi chạy Client từ C++** | `CreateProcessW` lỗi Error 2 | `ShellExecuteW` Windows Shell | ✅ Mở game mượt mà |
| **Độ ổn định Nút Start** | Nhấp nháy xanh/đỏ mỗi 1.0s (Crash loop) | Chuyển trạng thái dứt khoát 1 lần | ✅ Triệt tiêu hiện tượng Flicker |
| **Bảo vệ Core Engine** | `taskkill /F` tự sát sau mỗi 1 giây | Bảo lưu tiến trình, Startup Mutex | ✅ Uptime Core đạt 100% |
| **Kích hoạt Phím Nóng** | Dính phím ma từ bit LSB `state & 1` | Chỉ bắt `0x8000` kèm Debounce 0.8s | ✅ Không kích hoạt giả |
| **Khóa An Toàn Ngoại Vi** | Rò rỉ click/move ra Desktop khi chưa mở game | 4-Tier Safety Interlock (Drop input tại Tier 1) | ✅ Triệt tiêu 100% rò rỉ Desktop |
| **Xác thực bản đồ giữa map** | Treo "chờ xác thực vị trí bản đồ" dù đã ở Hive Fortress | Seed đuôi Client.txt + SHM `area_name` + `find_poe2_client_log` | ✅ Không kẹt chờ; không mở U trên map chiến đấu |
| **Kiểm Thử Hợp Nhất** | 661 C++ Checks / 50 Python Tests | Xem stdout lần chạy kiểm thử hiện tại | Chạy `AutoPOE2_Tests.exe` + `pytest` sau mỗi thay đổi |

---

## 5. ĐỒNG BỘ HÓA TÀI LIỆU & ZERO DOCUMENTATION DRIFT

Tuân thủ nghiêm ngặt **Điều 6 của AGENTS.md / GEMINI.md**:
- Kế hoạch tổng thể: Đã cập nhật vào [**`docs/development/plans/2026-09-07_ke_hoach_nang_cap_do_muot_thuong_mai_va_humanized_input.md`**](file:///c:/Projects/AutoPOE2/docs/development/plans/2026-09-07_ke_hoach_nang_cap_do_muot_thuong_mai_va_humanized_input.md#L111-L210).
- Đặc tả kỹ thuật nền tảng vòng đời: Lưu trữ chính thức tại [**`docs/development/10_client_lifecycle_and_hotkey_safety_protocol.md`**](file:///c:/Projects/AutoPOE2/docs/development/10_client_lifecycle_and_hotkey_safety_protocol.md).
- Đặc tả kỹ thuật khóa an toàn phần cứng tiền cảnh: Lưu trữ chính thức tại [**`docs/development/11_foreground_hardware_interlock_and_desktop_safety_protocol.md`**](file:///c:/Projects/AutoPOE2/docs/development/11_foreground_hardware_interlock_and_desktop_safety_protocol.md).
- Mã nguồn đối soát: Khớp hoàn toàn với `src/launcher/main.cpp`, `src/assistant_tool/client_launcher.py`, `src/assistant_tool/core_controller.py`, `src/assistant_tool/autonomous_lifecycle.py`, `src/assistant_tool/config.py`, `src/assistant_tool/log_watcher.py`, `src/assistant_tool/control_center.py`, `src/core/sensor/log_sensor.cpp`, `src/core/input/kmbox_net.hpp`, `src/core/input/kmbox_net.cpp`, `src/core/main.cpp`.

