# 61. Thẩm Định Lỗ Hổng Tự Hành & Căn Cứ Phát Triển Tiếp (Autonomy Gap Audit)

- **Mã tài liệu**: `SPEC-DOC-61-AUTONOMY-GAP-AUDIT-AND-NEXT-DEVELOPMENT-BASIS-20260915`
- **Mốc thời gian**: RCA `15/09/2026` · leftover **CODE-PRESENT** `16–18/09/2026` · **đính chính tài liệu `18/09/2026`**
- **Phiên bản tham chiếu**: Path of Exile 2 **v0.5.5+**, client build `6AA2213C` (`TimeDateStamp 0x6AA2213C`), AutoPOE2 IPC `PROTOCOL_VERSION = 0x00050005`
- **Nhân vật tham chiếu**: `BEONGUSI#3653` — Monk / Martial Artist CI, Lv 90, `HP 1/1`, Shoreline Hideout
- **Trạng thái**: `[SSoT HÀNG ĐỢI TỰ HÀNH]` — thay Doc 43. **Vòng Hideout→TRAVERSE→map chưa LIVE-VERIFIED.**
- **Phạm vi**: Đối chiếu mã `src/core/**`, `src/assistant_tool/**`, `tools/autonomous_mapping_coordinator.py` với hành vi game thủ POE2 WASD.
- **Độc giả**: Mir và mọi agent sửa Core, Companion, test, hoặc tài liệu
- **Skill điều khiển**: [`.agents/skills/poe2-human-controls/SKILL.md`](../../.agents/skills/poe2-human-controls/SKILL.md)

> **Đính chính 18/09/2026 — đọc mục 5 trước mục 1–3.**  
> Mục 1.3, mục 2 (GAP-1…7 “code 15/09”), mermaid mục 3 là **ảnh chụp RCA**. Trong tree 18/09: GAP-1…7 đã `[x]` unit-test; **leftover mục 5** mới là hàng đợi (`CODE-PRESENT`, chưa live).  
> Cấm mở ticket mới cho `MoveMouseSmooth`, cấm `VK_U` Map Device, dwell WASD ≥300ms, cấm phím F interact — những thứ đó **đã có trong code**. Cấm Spec 33. Cấm ghi `LIVE-VERIFIED` cho vòng Hideout→map.

---

## 0. Cách dùng tài liệu này (bắt buộc)

Tài liệu này là **Single Source of Truth** cho hàng đợi tự hành sau ngày 15/09/2026. Spec 00–60 mô tả *module, RCA cục bộ, và nghiệm thu từng phần*. Tài liệu 61 khóa *ranh giới: bot chưa thay được người chơi, và thứ tự sửa gốc*.

| Nếu định làm việc | Đọc trước | Không được làm |
|---|---|---|
| Bất kỳ thay đổi tự hành / F8 / Map Device / WASD | **Mục 5 leftover** rồi 6–7 (mục 1–2 = RCA 15/09, không phải ticket mới) | Đánh `[COMPLETED]` / `LIVE-VERIFIED` khi mới có FSM, pytest, hoặc một phiên map 60s đã đứng sẵn trong instance |
| Click UI (Atlas, inventory, TRAVERSE, portal) | Mục 2.GAP-1 + bất biến `INV-INPUT-ABS-MOUSE` | Gọi `KMBoxNet::MoveMouse` với tọa độ tuyệt đối; bấm phím **U** để mở Map Device |
| Di chuyển nhân vật | Mục 2.GAP-2 + Rule 18 / skill human-controls | Micro-tap WASD `< 150ms`; pulse-and-release 75/85ms |
| Combat / flask / dodge | Mục 2.GAP-4; doc 35/36 là schema, không phải “đã chơi như người” | Coi tracker combo/rage local là tài nguyên game; spam LMB/RMB khi không có mục tiêu |
| Full-auto thay người (campaign, PoB, Atlas tree) | Spec 33 — toàn bộ `[PLANNED]` | Làm trụ cột Spec 33 trước khi GAP-1 và GAP-2 đóng cổng live |

**Ba tầng trạng thái — bắt buộc ghi đúng một trong ba:**

| Nhãn | Nghĩa | Ví dụ đúng tại 15/09/2026 |
|---|---|---|
| **LIVE-PROVEN** | Có log/RAM/ảnh client thật, lặp lại được, không mâu thuẫn với code hiện tại | CI vitals `HP 1/1` + ES; XYZ động PlayerFinder; portal phím `.` (không phải T); WASD mode detect |
| **CODE-PRESENT** | Có module + (thường) unit test; **chưa** chứng minh vòng người-chơi trên client | BotBrain, MapDeviceHandler, SkillEngine, CharacterFSM, loot JSON, JPS |
| **PLANNED** | Chỉ spec hoặc skeleton | Campaign DAG, PoB, NavMesh ghép tile, boss phase, stash theo tab RAM, fog→frontier Core |

> Code có ≠ vòng farm chạy. Pytest pass ≠ nhân vật mở được Map Device. Một map 60s khi nhân vật **đã đứng trong instance** ≠ tự hành như game thủ.

### 0.1. Quan hệ với tài liệu cũ (chống lệch pha)

| Tài liệu | Vai trò từ 15/09/2026 |
|---|---|
| [Doc 43](./43_current_status_and_next_development_basis.md) | Ảnh chụp P0 (13/09), **stub → archive**. Không còn SSoT “làm gì tiếp”. |
| [Doc 59](./59_modular_test_architecture_and_endgame_mapping_loop.md) | Phân rã test suite. Nhãn 2-map LIVE **không** = leftover mục 5 đã LIVE-VERIFIED. |
| [Doc 60](./60_hideout_corner_stuck_prevention_and_in_map_autonomous_flow.md) | Vá triệu chứng Hideout (15/09), **stub → archive**. leftover: mục 5. |
| [Doc 29](./29_botbrain_priority_fsm_pickit_and_closed_farming_loop.md) | Hợp đồng arbiter / pickit. P0–P5 = CODE-PRESENT. |
| [Doc 33](./33_spec_fully_autonomous_gameplay_and_human_replacement_engine.md) | Tầm nhìn 8 trụ cột thay người. Cấm triển khai trước GAP-1…GAP-3. |
| [Doc 50](./50_two_tier_asymmetric_hybrid_architecture_specification.md) | Kiến trúc hai tầng — không thay. |
| [Doc 68](./68_poe2_market_pricing_and_dynamic_pickit_specification.md) | Tích hợp POE2 Scout API, quy đổi Chaos Orb, Dynamic Pickit >= 1 Chaos và C++ Hot-reload. |
| [Doc 69](./69_autonomous_endgame_loop_closure_and_resilience_specification.md) | Khép kín chu trình tự hành Endgame: Hậu cần Hòm đồ (Stash & Restock), Lọc Mod Brick và Phục hồi sự cố. |

---

## 1. Ảnh chụp hiện trạng (15/09/2026)

### 1.1. Một câu

AutoPOE2 là **co-pilot 120Hz** (vitals, reflex flask/dodge, combo ngắm-bấm trong một instance) cộng **macro UI hở vòng**. Nó **chưa** là vòng chơi khép kín của game thủ: Hideout → Map Device 3D → chọn node → TRAVERSE → packing map → loot → portal `.` → stash → map tiếp.

### 1.2. Chuyện đã LIVE-PROVEN (được phép dựa vào)

Giữ nguyên các bất biến P0 của doc 43/45/46 nếu code hiện tại vẫn khớp:

| Phân hệ | Bằng chứng neo | File |
|---|---|---|
| Pointer chain HP/ES/Ward/Mana (CI `1/1`) | Deserted / Mineshaft 13/09 | `pointer_chain_resolver.*`, `game_session.*` |
| XYZ động, từ chối neo `0x290` / `0x540` | PlayerFinder 16MB, ΔY liên tục | `player_finder.*`, doc 46 |
| Town Portal = `.` (`VK 0xBE`), không phải T | Config `use_portal_scroll=190` | `unstuck_handler`, `reflex_manager`, `quest_navigator` |
| WASD mode từ `[GENERAL]` / `[WASD_ACTION_KEYS]`, không tin cờ POE1 một mình | `ReadPoe2GameSettings` | `main.cpp` |
| Pulse E mặc định TẮT | `pulseEWithForward = false` | `quest_navigator.hpp` |
| Reflex CI: không Life Flask khi `maxHP==1`; dodge/chicken theo ES | `--ci` | `reflex_manager.*` |
| Focus interlock | Input no-op khi POE2 không foreground | `kmbox_net.*` |
| SHM seqlock + overlay F8/F6 | Vận hành hàng ngày | `shared_memory.*`, Control Center |

### 1.3. Chuyện CODE-PRESENT nhưng bị nhầm là “đã tự hành”

> **Lịch sử 15/09.** Cột “Vì sao” mô tả bug **trước** vá 16–18/09. Đối chiếu mục 5: chuột tuyệt đối / cấm U / dwell 300ms **đã vào tree**; cổng còn lại = leftover + nghiệm thu live.

| Phân hệ | Có gì | Vì sao chưa phải người chơi (ảnh 15/09) |
|---|---|---|
| BotBrain arbiter | Propose → 1 Execute/tick | Không mô hình “ván chơi”; F8 không tắt Process |
| CharacterFSM | Hideout → MapDevice → InMapExploring | `InMapCombat` / `InMapLooting` / `InMapPortalExit` **không bao giờ được chuyển tới** |
| MapDeviceHandler | FSM approach/open/waystone/activate/portal | `MoveMouse` tương đối + LMB skill + không TRAVERSE + không verify UI |
| Python mapping coordinator | OCR TRAVERSE, quét hàng 1 waystone | **Vẫn bấm U** (Atlas xem-only) |
| QuestNavigator + JPS | Explore góc + heatmap | WASD 85ms; lưới mặc định mọi ô walkable |
| ComboManager + SkillEngine | Profile Hollow Form TOML | Combo/rage đếm local; dodge theo ΔHP; không telegraph |
| LootController | Filter JSON, click <5ms | Bán kính 35u, không đi tới đồ; `moveMode` chết |
| Inventory / NPC FSM | Opcode 5/6/8 | Tọa độ UI; không tự chạy sau khi về Hideout |

### 1.4. Hiện trường mâu thuẫn với nhãn LIVE (bắt buộc nhớ)

Sáng 15/09/2026, PID `27856`, Shoreline Hideout 2560×1440, F8 ON (doc 60):

1. Nhân vật tung chiêu tại chỗ (LMB = skill WASD).
2. Click lệch góc dưới-phải, chạy rúc bờ đá.
3. Unstuck spam `.` → `Client.txt`: `Portals are disabled here.`

Doc 60 đã vá: fallback không còn `(960,540)`, backoff 3s, cấm portal trong Hideout. RCA mục 2 bên dưới **không** phải backlog copy-paste — leftover hiện tại ở **mục 5**. Vòng người-chơi vẫn chưa LIVE-VERIFIED.

---

## 2. Bảy lỗ hổng gốc (GAP) — nguyên nhân, không triệu chứng

Mỗi GAP: bất biến bị phá, vị trí code, hệ quả người chơi thấy, cổng đóng.

### GAP-1 — Lớp chuột/UI Map Device hở vòng (P0 tự hành)

> **RCA 15/09 — không mở ticket mới.** Bản 18/09 trong tree: mục 5 GAP-1 `[x]` (`MoveMouseSmooth`, cấm U, LMB ≤18u). Leftover: TRAVERSE coords + verify UI live.

**Bất biến bị phá (lúc RCA):** `INV-INPUT-ABS-MOUSE`, `INV-WASD-NO-BLIND-LMB`, `INV-KEY-NO-U-FOR-MAP-DEVICE` (Rule 18).

**Code:**

- `KMBoxNet::MoveMouse(short rx, short ry)` = **delta tương đối** (`mouse_event(MOUSEEVENTF_MOVE, rx, ry)`). Combat/QuestNav dùng `MoveMouseSmooth` (tuyệt đối). `MapDeviceHandler` **không có** `HumanCurveGenerator`.
- `map_device_handler.cpp`: `OpenDevice` / `SelectAtlasNode` / `PlaceWaystone` / `Activate` / `EnterPortal` gọi `MoveMouse(targetScreenX, targetScreenY)` rồi `ClickMouse(1, …)` (LMB).
- `CharacterFSM::TriggerMapDeviceRoutine` → `StartMapDevice(0, 0, false)` → **bỏ chọn node Atlas**.
- `tools/autonomous_mapping_coordinator.py` dòng ~759: `press_key(VK_U)` sau khi click device — Atlas **xem-only**, không khay Waystone, không TRAVERSE.
- C++ Activate click `activateButtonX/Y = (420, 660)` @1080p; **không** bước TRAVERSE; **không** đọc UI đã mở (doc 60 mô tả verify — **không có trong `.cpp`**).

**Hệ quả:** F8 tại Hideout = LMB skill + con trỏ bay một đoạn cỡ tọa độ 1440p + click đất = rúc góc. Hai não (C++ FSM + Python 1Hz) click chồng.

**Đóng khi (DoD live):**

1. Mọi click Map Device / Atlas / inventory / TRAVERSE / portal đi `MoveMouseSmooth` (hoặc SetCursorPos + delta đúng).
2. Không còn `press_key(VK_U)` trên đường Hideout Map Device.
3. Interact bệ đá: tới cự ly `<= 18u` bằng WASD giữ phím, `MoveMouseSmooth` tới tâm thực thể rồi **Click LMB có chủ đích** (TUYỆT ĐỐI CẤM phím F - phím F là Skill 8 của Monk; `INV-KEY-NO-F-FOR-INTERACT`, `INV-WASD-NO-BLIND-LMB`).
4. Có bước TRAVERSE (OCR hoặc tọa độ đã scale) **sau** khi xác nhận UI Endgame mở (template/OCR chữ TRAVERSE / khay waystone).
5. Log một lần: Hideout → portal map **không** `Portals are disabled here`, ΔXYZ tiếp cận device ≠ 0, ảnh UI Atlas trước TRAVERSE.

---

### GAP-2 — WASD micro-tap, nhân vật xoay chứ không bước (P0 tự hành)

> **RCA 15/09 — không mở ticket mới.** Bản 18/09: mục 5 GAP-2 `[x]` (hold-until-waypoint ≥300ms). Leftover: nghiệm thu ΔXYZ live.

**Bất biến bị phá (lúc RCA):** Rule 18 / skill human-controls — dwell bước ngắn **300–500ms**, đường dài **1.0–2.0s**, cấm `< 150ms`.

**Code:**

- `QuestNavigator::SendDirection`: `sleep 85ms` rồi KeyUp (cả emulation lẫn KMBox).
- `MapDeviceHandler` approach: `sleep 75ms`.
- Cadence 200/450ms chỉ khi `moveMode == MOUSE`.
- Unstuck radial WASD ~180ms — vẫn dưới ngưỡng người.

**Hệ quả:** Nhân vật quay mặt, AntiStuck 4s / Unstuck 10s mới kích. Hideout không tới được Map Device dù FSM “ApproachDevice”.

**Đóng khi:**

1. KeyDown giữ đến waypoint / hết dwell cấu hình (≥300ms ngắn, ≥1000ms đường dài), KeyUp khi đổi hướng hoặc tới nơi.
2. Unit test: SendDirection WASD dwell ≥ 300ms (reproduction: 85ms FAIL trước khi sửa).
3. Live: từ spawn Hideout, ΔXYZ liên tục khi F8 approach device; không đứng yên 4s rồi trượt góc.

---

### GAP-3 — Pathfinding trên lưới “mọi ô đều đi được” (P0 điều hướng)

**Bất biến bị phá:** `INV-NAV-TERRAIN-COMMERCIAL` (đi được trên địa hình thật, không giả định layout Hideout).

**Code:**

- `TerrainGrid` ctor / `Initialize`: `CELL_WALKABLE` toàn cục.
- `GameSession::AutoDetectTerrain` chỉ gọi khi `LoadingFinished` (`main.cpp` ~1152). Bật bot giữa Hideout = không nạp terrain.
- `RecenterIfNeeded`: lưới mới lại walkable, chỉ copy blocked cũ — **không** `LoadFromNativeTerrain`.
- `NativeTerrainData.worldOriginX/Y` mặc định 0, **không gán** khi extract.
- Map Device `ComputeTangentSlide(..., nullptr, ...)` — không có grid.
- Explore = góc + heatmap (`quest_navigator.cpp` ~626–710), **không** đọc exploration/fog buffer (doc 42 thuộc Visual Tool, Core không consume).

**Hệ quả:** JPS lập kế hoạch xuyên tường; packing = đẩy hướng; full clear không có nghĩa.

**Đóng khi:**

1. `worldOrigin` calibrate từ struct địa hình; `IsWalkableWorld` khớp ΔXYZ thực.
2. Recenter resample native; approach Map Device **cấm** `grid == nullptr`.
3. Live: tiếp cận device không đi xuyên bờ đá; trong map không path xuyên tường đã blocked trên minimap người chơi.

---

### GAP-4 — Combat ngắm-bấm, không chơi build (P1 trong map)

**Bất biến bị phá:** tài nguyên/build phải đọc từ game, không đếm phím; dodge theo nguy hiểm, không chỉ ΔHP.

**Code:**

- Tick: `ReflexManager` (ngưỡng %) → BotBrain → `ComboManager::Update`.
- SkillEngine: `OnSkillExecuted` tăng tracker; `OnSkillCastStarted` **không được gọi** → recovery stub.
- Combo/rage/charges: `IncrementTracker` / `m_state.currentComboPoints++` khi bấm.
- Dodge: burst ΔHP/ES ≥ 22%, Space tap, không telegraph.
- `close_targets` parse TOML, `IsEligible` không dùng. `nearbyEnemyCount = packet.entityCount` (mọi entity).
- Rarity/boss heuristic HP (`entity_manager` proximity: `>10000` rare, `>15000` boss).
- `activeWeaponSet = 1` hardcode. Flask chỉ `[1]` `[2]`.
- Python `tactical_brain` / `gem_manager` không đẩy lệnh skill xuống Core.

**Hệ quả:** Pack thì spam; boss không phase/kite; góc tường không “đừng lùi vào wall”. `docs/agent/pro_player_tactics.md` là giáo trình, không phải runtime.

**Đóng khi (tối thiểu P1, chưa phải Spec 33 boss FSM):**

1. Combo/rage/charges/weapon set đọc RAM hoặc fail-closed (không giả).
2. `OnSkillCastStarted` được gọi trên mọi AimAndCast của SkillEngine.
3. Live một map vàng: không chết vì đứng trong pack; dodge không spam khi không bị hit; rotation Hollow Form khớp action bar thật (T/MMB/Q/R/RMB/LMB), không LMB khi đang WASD nhầm skill.

---

### GAP-5 — Perception thiếu để quyết định như người (P1)

| Tín hiệu | Code | Lỗ |
|---|---|---|
| EntityMap chính | `ClassifyEntityType(name, …)` | Ổn khi offset sống |
| Fallback `ScanProximityMonsters` | `ent.type = 1` luôn | Mất loot / portal / device / NPC |
| `maxEntityDistance` | 150u | Ngoài 150 = mù |
| Loot | `lootRadius = 35`, `ExecutePickup` chỉ click | Không walk-to; `LootConfig.moveMode` không đọc |
| Map Device | substring `"device"` / `"atlas"` | Miss tên mã hóa / false positive |

**Đóng khi:** Fallback giữ taxonomy; loot Propose được đồ ngoài 35u → Explore/Move tới rồi mới click; live radar có item type 2 và portal type 3 khi chúng tồn tại trên màn hình.

---

### GAP-6 — Không có “map xong” và không có chu kỳ Hideout (P1 vòng)

**Code:**

- `CharacterFSM::InMapExploring` không bao giờ → Combat/Loot/PortalExit.
- Portal về gắn `bossSpotted` → mất entity rarity 3. `FULL_CLEAR` không portal.
- Python lifecycle tự bịa `lifecycle_state` từ tên khu (`core_shm_bridge.py`), enum lệch C++ (`HIDEOUT_IDLE=1` vs C++ `TownIdle=1` / `HideoutIdle=2`).
- `HideoutIdle` + autonomous → Map Device ngay, **không** stash/vendor/identify.
- F8 gán `allowPhysicalMove` cho CharacterFSM; `botBrain.Process` **không** bị gate — overlay PAUSE, tay vẫn giật.

**Đóng khi:**

1. `CharacterState` ghi SHM; Python chỉ opcode, không click song song.
2. `botBrain.Process` tắt khi F8 pause.
3. Tiêu chí xong map: timer **hoặc** pack clear **hoặc** boss — cấu hình, không chỉ heuristic boss.
4. Về Hideout: inventory gate (túi ≥ ngưỡng → Hooded One / stash) **trước** Map Device lần sau.

---

### GAP-7 — Tầm nhìn Spec 33 vẫn PLANNED (P2+, cấm làm trước GAP-1…3)

Campaign Quest DAG, PoB2 tree, gem socket, NavMesh ghép tile, boss phase/stagger window, stash Ctrl+Click theo tab RAM, waystone mod blacklist live, Atlas passive allocator, circadian, CTD watchdog.

**Không** đưa vào sprint hiện tại.

---

## 3. Vòng chơi đối chiếu: người vs bot

```mermaid
flowchart LR
    subgraph PRO["Game thủ POE2"]
        A1["WASD giữ phím tới bệ đá <= 18u"] --> A2["MoveMouse + Click LMB bệ đá"]
        A2 --> A3["Atlas Endgame + node xanh"]
        A3 --> A4["Ctrl+click Waystone"]
        A4 --> A5["TRAVERSE"]
        A5 --> A6["Space đóng panel"]
        A6 --> A7["Click LMB vào cổng portal"]
        A7 --> A8["Packing theo minimap"]
        A8 --> A9["Combat + dodge telegraph"]
        A9 --> A10["Đi tới loot"]
        A10 --> A11[". portal về"]
        A11 --> A12["Stash / identify"]
        A12 --> A1
    end

    subgraph BOT["AutoPOE2 15/09/2026"]
        B1["F8 → StartMapDevice bỏ Atlas"] --> B2["WASD 75ms"]
        B2 --> B3["MoveMouse absolute-as-relative + LMB skill"]
        B3 --> B4["Python bấm U xem-only"]
        B4 --> B5["Activate pixel mù"]
        B5 --> B6["Nếu may vào map: reflex % + spam bind"]
        B6 --> B7["Explore góc / loot 35u"]
        B7 --> B8["Portal nếu 'boss biến' hoặc timer Python"]
        B8 --> B1
    end
```

| Giai đoạn | Người | Bot 15/09 | Nhãn |
|---|---|---|---|
| Hideout idle | Chờ / stash | F8 → Map Device ngay | CODE-PRESENT |
| Tới Map Device | WASD giữ, men tường | 75ms + grid nullptr | LỖ GAP-2/3 |
| Mở device | LMB verified click khi sát <= 18u | LMB + chuột tương đối | LỖ GAP-1 |
| Atlas / TRAVERSE | Node + TRAVERSE | C++ bỏ node; Python U | LỖ GAP-1 |
| Trong map | Packing fog | Góc + heatmap | LỖ GAP-3 |
| Combat | Rotation + kite + telegraph | Ngưỡng + tracker giả | LỖ GAP-4 |
| Loot | Đi tới pile | Click ≤35u | LỖ GAP-5 |
| Về Hideout | `.` khi xong | Boss heuristic / timer | LỖ GAP-6 |
| Kinh tế | Stash rồi map tiếp | Map Device lại | LỖ GAP-6 |

---

## 4. Ma trận module (cập nhật 15/09/2026)

Ghi đè nhãn “LIVE-VERIFIED vòng farm / 2-map” trong doc 43 mục 5.P1-3 và doc 59 khi mâu thuẫn với GAP-1.

### 4.1. LIVE-PROVEN

Giống mục 1.2. Thêm: cấm portal Unstuck trong Town/Hideout (**code** `unstuck_handler.cpp` ~217–220, vá doc 60).

### 4.2. CODE-PRESENT (hàng đợi GAP)

BotBrain, CharacterFSM, MapDeviceHandler, QuestNavigator, ComboManager, SkillEngine, LootController, Inventory/NPC handlers, Python `AutonomousLifecycleManager`, `autonomous_mapping_coordinator.py`, OpticalInventoryScanner, WaystoneAutoCrafter (SKIP_CRAFT rare — unit + một log live 14/09, **chưa** gắn vòng F8 tin cậy).

### 4.3. PLANNED

Toàn bộ Spec 33 trụ 1–5, 7–8; fog exploration buffer → Core pathfinding; pickit affix RAM; vendor salvage không pixel.

---

## 5. Hàng đợi phát triển (làm theo thứ tự, không nhảy pha)

Nhóm **GAP-*** để không đụng P0–P2 cũ (doc 43 đã đánh `[x]` cho việc *đi được trong map khi đã vào instance*). Điều kiện xong = **cổng nghiệm thu**, không phải build pass.

### GAP-1. Chuột tuyệt đối + cấm U + TRAVERSE + LMB verified click (CẤM phím F) — `[x] ĐÃ HOÀN THÀNH (16/09/2026)`

- **Việc đã thực hiện:** `MapDeviceHandler` và `NpcInteractionHandler` tích hợp `HumanCurveGenerator` và chuyển 100% sang `MoveMouseSmooth` (`INV-INPUT-ABS-MOUSE`). Xóa hoàn toàn lệnh `press_key(VK_U)` (`INV-KEY-NO-U-FOR-MAP-DEVICE`). Triệt tiêu hoàn toàn ảo giác phím 'F' interact (phím 'F' là Skill 8 trong config người dùng). Mọi tương tác với bệ đá Map Device, Portal, NPC, và Chest chuyển sang cơ chế: **WASD tiếp cận $\le 18\text{u} \rightarrow$ `MoveMouseSmooth` tới tâm thực thể $\rightarrow$ Click LMB có chủ đích** (`INV-WASD-NO-BLIND-LMB` & `INV-KEY-NO-F-FOR-INTERACT`).
- **Test harness:** Test 92 (`TestAutonomyGapResolution`) trong `tests/test_brain_fsm.cpp` kiểm chứng `OpenDevice` thực thi Click LMB và chuyển sang `SelectAtlasNode`. Pytest `tests/test_autonomous_mapping_coordinator.py` kiểm chứng Click LMB mở Map Device và bước vào Portal: đạt **32/32 PASSED**.

### GAP-2. WASD giữ phím ≥300ms — `[x] ĐÃ HOÀN THÀNH (16/09/2026)`

- **Việc đã thực hiện:** Triệt tiêu hoàn toàn cơ chế micro-tap 85ms trong `QuestNavigator::SendDirection`. Chuyển sang cơ chế **Hold-Until-Waypoint**: giữ phím WASD liên tục khi di chuyển cùng hướng (`m_currentlyHeldKeys`), chỉ KeyUp khi đổi hướng hoặc dừng lại (`StopMovement`). Khóa chặn ngưỡng dwell time đổi hướng `nowMs - m_lastDirectionChangeMs >= 300ms` trong `QuestNavigator::SendDirection`, dwell tiếp cận bệ đá trong `MapDeviceHandler` nâng lên 320ms, radial WASD trong `UnstuckHandler` nâng lên 350ms, `LootConfig.wasdDwellMs` đạt 300ms (`INV-WASD-MIN-DWELL`).
- **Test harness:** Test 92 kiểm chứng `QuestNavigator::StopMovement` giải phóng phím an toàn, từ chối đổi hướng khi dwell < 300ms (150ms giữ nguyên 'D'), và chỉ đổi sang hướng mới 'A' khi trôi qua ≥ 300ms: đạt **844/844 checks PASSED**.

### GAP-3. Terrain origin + resample + cấm slide null grid — `[x] ĐÃ HOÀN THÀNH (15/09/2026)`

- **Việc đã thực hiện:** Trong `TerrainGrid::RecenterIfNeeded`, khi lưới dịch chuyển và `m_hasNativeTerrain == true`, nạp lại toàn bộ ô mới từ `m_nativeTerrain.IsWalkableWorld(wx, wy)` thay vì gán mặc định `CELL_WALKABLE`. Bổ sung `MapDeviceHandler::SetTerrainGrid(TerrainGrid*)`, truyền `m_terrainGrid` thật vào `ComputeTangentSlide` trong `main.cpp`. Bổ sung quét `worldOriginX/Y` trong `TerrainReader` và tự động kích hoạt `AutoDetectTerrain` khi bật F8 nếu chưa có native terrain.
- **Test harness:** Test 92 kiểm chứng `TerrainGrid::RecenterIfNeeded` duy trì và resample từ `memory::NativeTerrainData`.

### GAP-4. Combat trung thực tối thiểu — `[x] ĐÃ HOÀN THÀNH (15/09/2026)`

- **Việc đã thực hiện:** `ComboManager::Propose` lọc quái vật sống thật (`ent.type == 1 && !(ent.extraFlags & 4) && ent.maxHP > 0`) trong bán kính 450u thay vì lấy `packet.entityCount` bẩn. Kích hoạt gọi `m_skillEngine->OnSkillCastStarted(decision.vkCode, nowMs)` trước `AimAndCast` để cập nhật trạng thái cast time và cooldown trung thực cho SkillEngine.
- **Test harness:** Test 92 kiểm chứng `ComboManager::Propose` nhắm chính xác vào quái sống duy nhất (entity ID 1) thay vì quai chết (entity ID 2) hoặc item drop (entity ID 3).

### GAP-5. Radar đủ loại + walk-to-loot — `[x] ĐÃ HOÀN THÀNH (15/09/2026)`

- **Việc đã thực hiện:** `LootConfig` mở rộng `lootRadius = 120.0f` và bổ sung `pickupRange = 22.0f`. Trong `LootController::Execute`, nếu `moveMode == MovementMode::WASD` và khoảng cách tới item `dist > 22.0f`, tự động kích hoạt **Walk-To-Loot**: dùng `WorldToWasd(dx, dy)` gửi phím WASD di chuyển tiếp cận với dwell 300ms (`INV-WASD-MIN-DWELL`). Khi `dist <= 22.0f`, mới gọi `ExecutePickup` nhấp nhặt đồ, loại bỏ hoàn toàn click chuột mù LMB khi ở xa.
- **Test harness:** Test 92 kiểm chứng `LootController` đề xuất item cách 60u và kích hoạt Walk-To-Loot WASD thành công.

### GAP-6. Một FSM chủ + F8 mute BotBrain + stash gate — `[x] ĐÃ HOÀN THÀNH (15/09/2026)`

- **Việc đã thực hiện:** 
  1. Khép kín các chuyển đổi trạng thái trong `CharacterFSM`: `InMapExploring` <-> `InMapCombat` (khi có quái sống) <-> `InMapLooting` (khi có đồ rơi) <-> `InMapPortalExit`.
  2. Gate `botBrain.Process` trong `main.cpp`: chỉ cho phép thực thi khi `allowPhysicalMove && !isPassiveCoPilotMode`. Khi F8 PAUSE (`!allowPhysicalMove`), gọi `questNav.StopMovement(kmbox)` để nhân vật đứng yên hoàn toàn (`INV-F8-MUTES-BRAIN`).
  3. Mã hóa trạng thái `CharacterFSM` vào 16-bit cao của `PlayerTelemetryData.movementFlags` trước khi ghi SHM. `core_shm_bridge.py` đọc trực tiếp `(mov_flags >> 16) & 0xFF` để đồng bộ chính xác `CharacterLifecycleState`, xóa bỏ hoàn toàn việc đoán mò từ chuỗi `area_name`.
- **Test harness:** Test 92 kiểm chứng Explore → Combat → Loot → Explore → `InMapPortalExit` sau 12s yên.

### GAP-7. Triệt tiêu xung đột hai não (Anti-Brain-Conflict Interlock) & Map Device Retry Polling — `[x] ĐÃ HOÀN THÀNH (15/09/2026)`

- **Việc đã thực hiện:**
  1. **Gatekeeper Interlock**: C++ Core `DispatchCommand` từ chối `TRIGGER_WORLD_MAP_TRAVEL` (Opcode 96) nếu `mapDeviceHandler->IsActive()` hoặc nhân vật đang ở Hideout và đích đến là Act 1-3. `TownQuestEngine::StartTravel` từ chối mở phím `U` khi ở Hideout (`INV-BRAIN-CONFLICT-GATEKEEPER`).
  2. **Lifecycle Sync**: `AutonomousLifecycleManager` và `AutoQuester` tự động phát hiện Hideout là `ENDGAME_EARNING` (nâng `player_level >= 65`), triệt tiêu vĩnh viễn việc phát lệnh travel về Act 1 The Riverbank khi đứng ở Hideout.
  3. **Retry Polling**: `MapDeviceHandler::WaitForPortals` thực hiện retry polling trong cửa sổ 8000ms tính từ khi click TRAVERSE, thay vì fail-closed sau 1 tick duy nhất tại 2500ms, đảm bảo bao quát hoạt ảnh mở portal của POE2 (`INV-MAPDEVICE-RETRY-POLL`).
- **Test harness:** C++ Test 93 (`TestBrainConflictGatekeeperAndPortalRetry`) và Pytest `test_autonomous_lifecycle.py` kiểm chứng toàn diện.

### GAP leftover 15/09/2026 (sprint đóng cổng P0) — `[x] CODE-PRESENT` — **chưa LIVE-VERIFIED**

Bản vá tiếp theo (cùng ngày) đóng các lỗ còn sót sau khi GAP-1…7 được đánh `[x]`:

| Hạng mục | Code |
|---|---|
| TRAVERSE 1080p `(806.25, 562.5)` + node Atlas mặc định `(1057.5, 189)` + chờ Atlas 1100ms | `data/ui/coordinates.toml`, `MapDeviceHandler` |
| Fail-closed `WaitForPortals` (không portal → Failed, cấm fallback `(960,400)`) + Retry Polling 8000ms | `map_device_handler.cpp` |
| Hold WASD không `sleep_for(320/350/300)` trên hot path | Map Device, Unstuck, Loot, AreaEvent |
| Pickup / rương / event: `MoveMouseSmooth` + **Click LMB có chủ đích** khi dist ≤ 18u (CẤM phím F - F là Skill 8) | `loot_controller`, `area_events`, `quest_navigator` TriggerInteract |
| Stash gate 4s khi về Hideout từ map; `WantsPortalExit` → phím `.` | `character_fsm`, `quest_navigator` |
| Dual-brain: Python skip Stage 2 nếu Core đang Approach/Open/Portal; cấm Opcode 96 tại Hideout | `autonomous_mapping_coordinator.py`, `autonomous_lifecycle.py`, `main.cpp` |
| Proximity fallback giữ type≠1 (loot/portal/device); `close_targets` | `entity_manager.cpp`, `skills_table.hpp` |
| F8 PAUSE nhả WASD Map Device / loot / unstuck / area event | `main.cpp` |
| Unstuck: nha WASD không `++attempt`; empty WASD vẫn hold W 350ms | `unstuck_handler.cpp` |
| Python Stage 2/3 fail-closed nếu OCR không thấy Atlas/TRAVERSE; Stage 4 click LMB vào Portal, cấm phím F | `autonomous_mapping_coordinator.py` |

**Cấm** ghi LIVE-VERIFIED Hideout→TRAVERSE→map cho đến khi có log client không `Portals are disabled here`, ΔXYZ tiếp cận device, và screenshot Atlas/TRAVERSE cùng build.

### Không làm trong sprint này

Spec 33 campaign/PoB/Atlas tree/CTD; Visual Mods nhúng lại Core (repo `Poe2VisualTool`); Life Flask trên CI; phím T làm portal; yêu cầu user dời Map Device gần Waypoint.

---

## 6. Bất biến mới (vi phạm = bug P0 tự hành)

| ID | Quy tắc | Kiểm tra |
|---|---|---|
| **INV-INPUT-ABS-MOUSE** | Mọi click UI/world-target dùng tọa độ tuyệt đối (`MoveMouseSmooth` / cursor pos). `MoveMouse(rx,ry)` chỉ nhận delta Bézier. | Grep `kmbox.MoveMouse(` trong `map_device_handler`, `npc_interaction_handler`, `inventory_handler`, `area_events` |
| **INV-WASD-NO-BLIND-LMB** | WASD: LMB = skill khi click đất trống. Cấm click mù xuống đất / chân nhân vật / pixel chưa verify UI. Tương tác thực thể: WASD tiếp cận <= 18u -> `MoveMouseSmooth` -> Click LMB có chủ đích. | OpenDevice / EnterPortal / loot miss |
| **INV-KEY-NO-F-FOR-INTERACT** | TUYỆT ĐỐI CẤM gửi phím F để tương tác thế giới. Phím F là Skill 8 của Monk; tương tác thực thể 100% bằng Click LMB có chủ đích tại cự ly <= 18u. | QuestNavigator, NpcInteractionHandler, MapDeviceHandler |
| **INV-KEY-NO-U-FOR-MAP-DEVICE** | Hideout Map Device **không** gửi `VK_U`. U = World Map làng (TownQuestEngine) hoặc Ctrl+U Atlas passives. | Grep `VK_U` / `0x55` trong `autonomous_mapping_coordinator.py`, lifecycle endgame |
| **INV-WASD-MIN-DWELL** | Dwell WASD ≥ 300ms (ngắn) / ≥ 1000ms (dài). Cấm sleep 75/85ms rồi KeyUp. | `SendDirection`, Map Device approach, Unstuck |
| **INV-FSM-SINGLE-OWNER** | Một chủ vòng đời: C++ `CharacterFSM` ghi SHM. Python không click song song khi handler đang `IsActive()`. | Trace F8 Hideout |
| **INV-F8-MUTES-BRAIN** | `allowPhysicalMove == false` ⇒ không `botBrain.Process` Execute vật lý (Reflex flask/chicken vẫn được nếu policy Co-Pilot). | F8 PAUSE live |
| **INV-MAPDEVICE-VERIFY-UI** | Không AdvanceFsm Open→Waystone→Activate nếu chưa có tín hiệu UI (OCR TRAVERSE / khay / tab ENDGAME). Timeout → Failed + backoff, không click đất. | Log + screenshot |
| **INV-TERRAIN-ORIGIN** | `worldOrigin` ≠ giả định 0 trừ khi struct thật origin 0. Slide/JPS cấm `grid == nullptr` khi đang path. | TerrainReader + Map Device |
| **INV-DOC-NO-LIVE-WITHOUT-LOOP** | Cấm ghi LIVE-VERIFIED cho vòng Hideout→map khi leftover mục 5 chưa đóng trên client cùng build. | Review spec |
| **INV-BRAIN-CONFLICT-GATEKEEPER** | C++ Core và TownQuestEngine cấm nhận Opcode 96 (`TRIGGER_WORLD_MAP_TRAVEL`) khi ở Hideout hoặc khi MapDevice đang active. | Test 93, `main.cpp`, `town_quest_engine.cpp` |
| **INV-MAPDEVICE-RETRY-POLL** | `MapDeviceHandler::WaitForPortals` retry polling trong 8000ms, cấm fail-closed ngay sau 1 tick 2500ms. | Test 93, `map_device_handler.cpp` |

Bất biến P0 cũ (doc 43 mục 2) **vẫn hiệu lực:** `INV-BUILD-01`, `INV-MEM-DYNAMIC-XYZ`, `INV-CI-01`, `INV-KEY-PORTAL`, `INV-KEY-WASD`, `INV-KEY-E-PULSE`, `INV-BRAIN-01`, `INV-CRAFT-WAYSTONE-01`, `INV-ANGE-EXCHANGE-01`.

---

## 7. Hợp đồng kiểm thử (Empirical DoD cho GAP)

### 7.1. Harness không cần game (bắt buộc trước merge)

```text
# Reproduction + regression (User-Mode)
# 1) WASD dwell ≥ 300ms (test FAIL trên 85ms trước khi sửa)
# 2) MoveMouse không nhận tọa độ kiểu (1170, 802) như đích tuyệt đối
# 3) Coordinator / lifecycle không press VK_U trên stage Map Device
# 4) CharacterFSM không chuyển InMapCombat (cho đến khi implement GAP-6 — test hiện trạng: các state đó unreachable)
```

Lệnh cụ thể do agent implement gắn vào `tests/`; **cấm** lấy số PASS cũ 726/741/751 làm bằng chứng GAP đã đóng.

### 7.2. Live (game foreground, Core elevated, 2560×1440)

```text
# Hideout loop (GAP-1 + GAP-2)
# - F8 tại Shoreline Hideout
# - ΔXYZ tiếp cận Map Device
# - Ảnh UI Atlas / TRAVERSE trước click
# - Vào map; Client.txt không có "Portals are disabled here"
# - F8 pause: nhân vật đứng, không click

# In-map (sau khi đã vào được)
# - python tools/autonomous_map_runner.py --ci --max-es <live> --wasd --move
# - Pos đổi; radar Mobs khi có pack; không Warcry-as-portal
```

### 7.3. Cấm dùng làm DoD tự hành

- `cmake --build` / `AutoPOE2_Tests.exe` xanh.
- Pytest mock Win32.
- Một phiên `autonomous_map_runner.py` 60s khi nhân vật **đã ở trong map** (đó là nghiệm thu P0 *trong instance*, không phải vòng người chơi).
- Nhãn COMPLETED trên doc 59/60.

---

## 8. Bản đồ tệp neo (agent sửa đúng chỗ)

| GAP | C++ | Python / tools |
|---|---|---|
| 1 | `src/core/brain/map_device_handler.cpp`, `src/core/input/kmbox_net.cpp`, `src/core/brain/character_fsm.cpp` | `tools/autonomous_mapping_coordinator.py`, `src/assistant_tool/autonomous_lifecycle.py` |
| 2 | `src/core/navigation/quest_navigator.cpp`, `map_device_handler.cpp` (75ms), `unstuck_handler.cpp` | Coordinator WASD helper nếu còn pulse ngắn |
| 3 | `terrain_grid.cpp`, `terrain_reader.*`, `game_session.cpp` `AutoDetectTerrain`, `main.cpp` LoadingFinished | — |
| 4 | `combo_manager.cpp`, `skill_engine.cpp`, `reflex_manager.cpp`, `entity_manager.cpp` (rarity) | Không đẩy skill từ `tactical_brain` cho đến khi IPC directive thật |
| 5 | `entity_manager.cpp` ScanProximity, `loot_controller.cpp` | pickit DSL ≠ hot path — đừng giả đóng |
| 6 | `character_fsm.cpp`, `main.cpp` tick F8, protocol telemetry | `core_shm_bridge.py` lifecycle_state |

Thư viện dùng chung bắt buộc: `src/core/common/` (`math2d` WorldToScreen/WorldToWasd), `src/core/input/` — cấm viết lại MoveMouse trong handler.

---

## 9. Việc không làm (cập nhật 15/09)

1. Không Life Flask trên CI, không respec Facebreaker.
2. Không tin `agent_memory.json` khi `Client.txt` area khác.
3. Không copy `.ipd` / DLL ExiledBot 2.
4. Không inject DXGI / ghi `Content.ggpk`.
5. Không `keybd_event` vượt Focus Interlock.
6. Không fake telemetry / `|| true`.
7. Không Spec 33 trụ 1–8 trước GAP-1…GAP-3.
8. Không đánh LIVE-VERIFIED vòng Hideout khi còn `MoveMouse(absolute)`, `VK_U` mapping, hoặc WASD 85ms.
9. Không yêu cầu user dời Map Device sát Waypoint.

---

## 10. Lệnh đọc tối thiểu cho agent (trước khi viết code)

1. Tài liệu này — mục 0, 2, 5, 6.
2. Skill [`.agents/skills/poe2-human-controls/SKILL.md`](../../.agents/skills/poe2-human-controls/SKILL.md).
3. `map_device_handler.cpp` `OpenDevice` + `kmbox_net.cpp` `MoveMouse` / `MoveMouseSmooth`.
4. `quest_navigator.cpp` `SendDirection`.
5. `tools/autonomous_mapping_coordinator.py` stage 2 (VK_U).

Khi đóng một GAP: đánh `[x]` tại mục 5 **kèm** timestamp + đường dẫn log/ảnh phiên đó. Không sửa nhãn LIVE-PROVEN chỉ vì commit.

---

## 11. Đặc Tả Pha 2 — Nhận Diện Sương Mù & Chiến Lược Di Chuyển Khi Mở Full Minimap (Full Reveal Sweep)

### 11.1. Vấn Đề Gốc Của Chế Độ Khám Phá Truyền Thống
- Khi người chơi sử dụng công cụ can thiệp đồ họa (`poe_visual_tool`, `poe_smoother`, hoặc tính năng `RevealEntireMinimap`), bộ đệm `ExplorationGridBuffer` (`AreaInstance + 0x9d0`) bị bão hòa 100% bằng dữ liệu `TerrainGridBuffer` (`+0x9b8`). Toàn bộ bản đồ con hiển thị sáng trưng ngay khi vừa tải map.
- Nếu thuật toán tự hành dựa vào "sương mù chưa mở" (Fog Frontier) để làm mục tiêu di chuyển:
  - Bot sẽ thấy toàn bộ map có tỷ lệ mở sáng đạt 100% ngay tại $t = 0\text{s}$.
  - Hậu quả: Coi như map đã khám phá xong $\rightarrow$ đứng yên, hoặc hủy chu trình và mở Town Portal về Hideout sớm, hoặc không thể tìm ra frontier để đi tiếp.

### 11.2. Cơ Chế Nhận Diện Tự Động (Fog Mode Auto-Detection)
Hệ thống tự hành phân loại chế độ khám phá theo 2 trạng thái: `FogMode::STANDARD_FOG` và `FogMode::FULLY_REVEALED`.
1. **Kiểm tra độ bão hòa Exploration Buffer tại thời điểm tải map ($t < 2.0\text{s}$)**:
   - Nếu `ExplorationGridBuffer` có tỷ lệ byte non-zero $> 50\%$ (thực tế $> 85\%$) ngay khi mới bước vào map $\implies$ **`FogMode::FULLY_REVEALED`** (Người dùng đang dùng Visual Tool / Smoother xóa sương mù).
   - Nếu tỷ lệ byte non-zero chỉ tập trung quanh chân nhân vật $< 5\%$ $\implies$ **`FogMode::STANDARD_FOG`** (Chế độ sương mù gốc của game).
2. **Kiểm tra tiến trình hỗ trợ**: Kiểm tra sự hiện diện của `AutoPOE2_VisualTool.exe` hoặc các module smoother đang chạy.
3. **Cấu hình cưỡng bức**: Hỗ trợ cờ cấu hình trong `bot_config.toml`: `[NAVIGATION] fog_mode = "auto" | "full_reveal" | "standard"`.

### 11.3. Chiến Lược Di Chuyển Pro Player Khi Đã Xóa Sương Mù (`FULL_REVEAL_SWEEP`)
Khi bản đồ đã mở sáng 100%, Pro Player không đi tuần tra mò mẫm. Họ tận dụng lợi thế tầm nhìn để quét map với tốc độ kỷ lục thông qua 3 trụ cột:

```mermaid
flowchart TD
    A["Vào Map Mới (t < 2.0s)"] --> B{"Tỷ lệ sáng > 50% hoặc VisualTool active?"}
    B -- "KHÔNG (< 5%)" --> C["FogMode::STANDARD_FOG<br/>(Dò biên sương mù Frontier truyền thống)"]
    B -- "CÓ (> 50%)" --> D["FogMode::FULLY_REVEALED<br/>(Kích hoạt Full Reveal Sweep)"]
    
    subgraph FRS["Chiến Lược FULL_REVEAL_SWEEP (Pro Player 3 Trụ Cột)"]
        D --> E1["Trụ Cột 1: ExplorationHeatmap Vết Chân Vật Lý<br/>(Lưới 128x128 ô 32u độc lập 100% với minimap)"]
        D --> E2["Trụ Cột 2: Pack-to-Pack Traversal<br/>(Gom cụm quái RAM Radar, lướt JPS bầy-sang-bầy)"]
        D --> E3["Trụ Cột 3: Men Tường Hình Học Topo<br/>(Topological Room-by-Room Sweep, Zero Backtracking)"]
    end
    
    E1 & E2 & E3 --> F["Quyết Định Hướng Đi Tối Ưu Nhất Cho Tick Hiện Tại"]
```

1. **Trụ Cột 1 — Lưới Vết Chân Vật Lý Độc Lập (`ExplorationHeatmap`)**:
   - `QuestNavigator` duy trì mảng `m_visitedHeatmap` kích thước $128 \times 128$ ô (mỗi ô 32u).
   - **Bất biến**: Lưới này **hoàn toàn độc lập với shader hay buffer minimap của game**. Ô lưới chỉ được đánh dấu khi nhân vật thực sự bước qua tọa độ thế giới tương ứng.
   - Vùng chưa đi qua là vùng có `GetVisitCount(x, y) == 0`. Thuật toán chấm điểm hướng đi phạt nặng các hướng đã đi qua (`score -= visits * 85.0f`), bảo đảm bao phủ 100% diện tích map mà không bao giờ lặp lại đường cũ.
2. **Trụ Cột 2 — Gom Cụm Quái & Di Chuyển Bầy-Sang-Bầy (`Pack-to-Pack Traversal`)**:
   - Tận dụng RAM Radar đọc danh sách quái sống trong toàn bộ phạm vi map.
   - Gom các quái vật thành các cụm (Packs: $\ge 3$ quái hoặc có quái Rare/Unique).
   - Sử dụng giải thuật tối ưu hóa đường đi (Greedy Nearest-Neighbor / TSP): Nhân vật lướt JPS liên tục từ cụm quái này sang cụm quái gần nhất tiếp theo. Tốc độ dọn map nhanh gấp 3 lần so với bot đi mò mẫm thông thường.
3. **Trụ Cột 3 — Men Tường & Quét Phòng Ốc Topo (`Topological Room-by-Room Sweep`)**:
   - Dựa vào ma trận `TerrainGrid` (đã nạp từ bộ nhớ RAM), bot phân tích cấu trúc đồ thị phòng ốc và ngã rẽ.
   - Áp dụng nguyên lý men tường (Perimeter-First), vét sạch các phòng rìa ngoài trước khi quét lõi trung tâm, triệt tiêu hoàn toàn nguy cơ bỏ sót ngách cụt.

---

## 12. Đặc Tả Pha 3 — Trừu Tượng Hóa Vai Trò Kỹ Năng (Skill Roles Architecture)

### 12.1. Bản Chất Combo Của Lớp Nhân Vật Monk / Martial Artist
- Trong POE2, nhân vật liên tục thay đổi kỹ năng theo cấp độ và trang bị. Việc gắn cứng tên kỹ năng (ví dụ: `IceStrike`) vào logic bot gây gãy vỡ hệ thống khi người chơi đổi sang `Tempest Flurry`, `Whirling Assault`, v.v.
- **Giải pháp**: Kiến trúc hóa toàn bộ bảng kỹ năng theo **Vai Trò Ngữ Nghĩa (Semantic Skill Roles)**:

| Slot Phím | Vai Trò Ngữ Nghĩa (`role`) | Gem Hiện Tại | Gem Trước | Điều Kiện Kích Hoạt & Nhiệm Vụ |
|---|---|---|---|---|
| **Phím Q** | **`COMBO_BUILDER`** | `MonkTempestFlurry` | `IceStrike` | Đòn đánh tốc độ cao tích lũy **1 $\rightarrow$ 4 Combo Points**. Mục tiêu: dồn combo nhanh nhất có thể. |
| **Phím R** | **`COMBO_FINISHER`** | `MonkTempestBell` | — | **BẮT BUỘC ĐỦ 4 COMBO POINTS HOẶC BOSS STAGGER $\ge 80\%$**. Cắm Chuông Bão kích nổ xung kích Lôi Điện cực đại. |
| **Chuột Phải (RMB)** | **`SUSTAINED_AOE_BUFF`** | `MonkChargedStaff` | — | Xoay gậy duy trì buff sát thương diện rộng và nạp thanh Swirling Spiral (duy trì 40s). |
| **Chuột Giữa (MMB)** | **`GAP_CLOSER_DEBUFF`** | `DruidWolfLeapAttack` | — | Hóa Sói nhảy áp sát từ xa và gán bùa debuff (Mark) lên Boss/Rare. |
| **Phím T** | **`PRE_BUFF_INITIATION`** | `DruidBearWarcry` | — | Gầm lấy tức thì 30 Rage trước khi giao chiến mục tiêu lớn. |
| **Chuột Trái (LMB)** | **`PRIMARY_CLEAR`** | `MartialArtistMantraofIllusionsSkill` | — | Dọn dẹp quái nhỏ lẻ tẻ, duy trì di chuyển linh hoạt. |
| **Spacebar** | **`MOBILITY_IFRAME`** | `DodgeRoll` | — | Lăn né bất tử; hủy đòn tức thì khi nguy cấp hoặc ES $< 70\%$. |
| **Phím F** | **`SKILL_8`** | `BoundSkill8` | — | Kỹ năng thứ 8 của người chơi. **TUYỆT ĐỐI CẤM DÙNG ĐỂ TƯƠNG TÁC THẾ GIỚI** (`INV-KEY-NO-F-FOR-INTERACT`). |

### 12.2. Khóa Chặn Bất Biến Combo (Combo Invariant Interlock)
- `SkillEngine` và `ComboManager` chỉ kiểm tra điều kiện kích hoạt dựa trên **Vai Trò (`role`) và Trạng Thái Tài Nguyên Game**, không phụ thuộc chuỗi tên gem:
  - `CanExecuteRole(SkillRole::COMBO_FINISHER) == (currentComboPoints >= 4 || targetStaggerPct >= 0.80f)`.
  - Nếu chưa thỏa mãn: Luôn ưu tiên thực thi `SkillRole::COMBO_BUILDER` (Phím Q) để tích điểm.

---

## 13. Giao Thức Snapshot Nhân Vật Có Cấu Trúc (Character Data Snapshotter Protocol)

### 13.1. Sự Cần Thiết Của Snapshotting Dữ Liệu Nhân Vật
- Trong Path of Exile 2, người chơi tiến hóa liên tục: lên cấp, tăng điểm nội tại (Passive Tree), thăng hoa (Ascendancy), nâng cấp đá kỹ năng (Skill Gems & Support Gems), thay đổi trang bị và tinh chỉnh chỉ số phòng thủ (Life/ES/Ward/Mana/Resistances).
- Để AI Agent có thể hiểu và làm chủ nhân vật như một game thủ chuyên nghiệp, hệ thống triển khai công cụ trích xuất trạng thái có cấu trúc: [`tools/character_snapshotter.py`](../../tools/character_snapshotter.py).

### 13.2. Cấu Trúc Đầu Ra Nhị Phân & Tài Liệu (Dual-Artifact SSoT)
1. **JSON Snapshot Máy Đọc (`character_target/snapshots/char_<NAME>_<TIMESTAMP>.json`)**:
   - Chuẩn schema: `autopoe2.character_snapshot/v1`.
   - Lưu trữ đầy đủ: Timestamp ISO 8601 (Local & UTC), Vitals chính xác từng điểm, bảng ánh xạ Skill Roles, Keystones, vũ khí trang bị, và ngữ cảnh tiến hóa.
2. **Nhật Ký Tiến Hóa Markdown (`character_target/CHARACTER_EVOLUTION.md`)**:
   - Lưu vết lịch sử tăng trưởng nhân vật theo từng mốc thời gian.
   - Ghi nhận chi tiết từng bước chuyển đổi (ví dụ: mốc chuyển từ `IceStrike` sang `MonkTempestFlurry` tại ES 4,713, Level 90 CI).
   - AI Agent đọc tài liệu này để tự động đồng bộ chiến lược combo và ngưỡng sinh tồn mà không cần lập trình lại.

