# 16. Kiến Trúc Khắc Phục Dò Tọa Độ XYZ Địa Hình Phẳng & Tối Ưu Hóa Syscall RPM (Sandswept Marsh Resolution)

- **Mốc thời gian**: 07/09/2026
- **Bối cảnh**: AutoPOE2 C++23 Core Engine & Memory Subsystem
- **Module phụ trách**: `PlayerFinder` (`src/core/memory/player_finder.cpp/.hpp`), `GameSession` (`src/core/game_session.cpp`)

---

## 1. Phân Tích Nguyên Nhân Gốc Rễ (Root Cause Analysis - RCA)

### 1.1 Hiện tượng lỗi trên thực địa
Người dùng di chuyển khắp bản đồ đầm lầy *Sandswept Marsh*, tuy nhiên HUD của bot liên tục hiển thị:
```text
XYZ: 0.0000, 0.0000, 0.0000 [!] CHƯA CÓ XYZ
```
Log hệ thống ghi nhận:
```text
[AutoScan] Da chup 52844 float baseline tai cong vao. Dang cho buoc di chuyen dau tien...
```
Hệ thống rơi vào trạng thái bế tắc (hang-up) vĩnh viễn ở `AutoPositionState::WAITING_FIRST_STEP` dù người chơi đã di chuyển hàng nghìn bước.

### 1.2 Bất biến kiến trúc bị vi phạm (Architectural Invariant Invalidation)
1. **Giả định sai về tính biến thiên đồng thời của 3 trục (3-Axis Delta Fallacy)**:
   - Trong `PlayerFinder::FindXYZTriples()`, thuật toán cũ kiểm tra:
     ```cpp
     if (addrs.count(f.first + 4) && addrs.count(f.first + 8))
     ```
     Đòi hỏi cả 3 ô nhớ liên tiếp (`A`, `A+4`, `A+8`) đều phải nằm trong danh sách `m_floats` (tức cả $X, Y, Z$ đều phải di chuyển $\ge \text{minDelta} = 0.8\text{f}$).
   - **Thực tế vật lý trong Path of Exile 2**: Khi nhân vật di chuyển trên địa hình phẳng (như đầm lầy Sandswept Marsh, sàn gỗ Dreadnought, hoặc bề mặt phẳng), cao độ $Z$ hoàn toàn không thay đổi ($\Delta z = 0.0\text{f} < 0.8\text{f}$).
   - Do đó, ô nhớ trục $Z$ bị loại khỏi `m_floats`, khiến `addrs.count(f.first + 8)` **luôn trả về `false`**. Thuật toán không bao giờ phát hiện được bộ ba tọa độ!
2. **Sai lệch ngưỡng kích hoạt trong `GameSession`**:
   - `src/core/game_session.cpp` dòng 993 trước đây có điều kiện `if (moved >= 3)`.
   - Khi di chuyển trên mặt đất phẳng hoặc di chuyển đơn trục, số float thay đổi chỉ là 1 hoặc 2. Điều kiện `>= 3` khiến `FindXYZTriples()` không bao giờ được kích hoạt.
3. **Bão gọi hệ thống (Syscall RPM Storm) do tràn bộ đệm baseline**:
   - `SnapshotBaselineFloats` thu thập tới 52,844 float không chọn lọc.
   - Trong vòng lặp so sánh 100ms của `FindMovedFromBaseline`, hệ thống gọi 52,844 lần `m_reader.ReadValue<float>()` đơn lẻ, tạo ra $> 528,000$ lệnh gọi hệ thống `NtReadVirtualMemory` mỗi giây, gây nghẽn CPU kernel và gián đoạn khung hình client game.

---

## 2. Giải Pháp Kiến Trúc Tận Gốc (Structural Architectural Fix)

### 2.1 Mở rộng Không gian Khảo sát Lân cận (Neighboring Coordinate Space Expansion)
Khi ghi nhận 1 float $A$ có biến thiên $\ge 0.8\text{f}$, float đó có thể thuộc về 1 trong 3 trục của `Vector3`:
- Nếu $A$ là trục $X$: Vector bắt đầu tại $A$.
- Nếu $A$ là trục $Y$: Vector bắt đầu tại $A - 4$.
- Nếu $A$ là trục $Z$: Vector bắt đầu tại $A - 8$.

Tất cả các địa chỉ khởi đầu hợp lệ được đưa vào tập hợp `candidateStarts` (khử trùng lặp).

### 2.2 Thuật toán Chấm điểm Đa trục (Multi-Axis Ground-Plane Scoring)
Đọc nguyên khối 12-byte tại mỗi `startAddr` bằng `ReadPositionAt(startAddr, x, y, z)` và xác thực qua `IsValidXYZ(x, y, z)`. Sau đó chấm điểm theo mức độ tin cậy:
- Trục $X$ đổi: $+100$ điểm.
- Trục $Y$ đổi: $+100$ điểm.
- Trục $Z$ đổi: $+50$ điểm.
- **Thưởng cực đại cho mặt phẳng chuyển động $2D$ $(X, Y)$**: $+200$ điểm.
- Cả 3 trục cùng đổi: $+300$ điểm.
- Khoảng cách gần thực thể `PlayerState` ($< 64\text{KB}$): $+300$ điểm.

### 2.3 Cơ chế Loại trừ Chồng lấn (Non-Overlapping Greedy Selection)
Sắp xếp các ứng viên theo điểm số giảm dần. Ứng viên điểm cao nhất được chọn trước; bất kỳ ứng viên nào nằm trong phạm vi chồng lấn 12-byte ($[\text{selected} - 11, \text{selected} + 11]$) đều bị loại bỏ triệt để.

### 2.4 Thu hẹp Bộ đệm & Batch Chunk Reading (Triệt tiêu Syscall Storm)
1. **Lọc Baseline có chọn lọc**:
   - `SnapshotBaselineFloats` ưu tiên quét các vùng nhớ gần `center` nhất.
   - Quét thu thập các cụm 3 float thỏa mãn `IsValidXYZ(x, y, z)` ngay tại thời điểm vào map, khống chế số lượng baseline float $< 5000$ (mặc định tối đa 4096 floats).
2. **Gộp đọc theo Chunk**:
   - Trong `FindMovedFromBaseline`, các float có khoảng cách lân cận $\le 128$ bytes được gom vào một lần đọc khối duy nhất (tối đa 64KB).
   - Số lượng syscall RPM giảm từ $52,844$ xuống còn $\approx 10 - 30$ calls mỗi chu kỳ 100ms (giảm $> 99.9\%$).

---

## 3. Bằng Chứng Xác Minh (Verification Evidence)

1. **Unit Test Tái hiện & Xác nhận (Test 13b - Sandswept Marsh Regression)**:
   - File: `tests/test_core.cpp`
   - Kịch bản: Mô phỏng địa hình phẳng đầm lầy Sandswept Marsh ($X = 12500 \to 12525$, $Y = 16800 \to 16830$, $Z = 150 \to 150$, $\Delta z = 0.0$).
   - Kết quả: `FindMovedFromBaseline` phát hiện 2 floats, `FindXYZTriples` thành công $100\%$ khóa tọa độ tại đúng `playerPosAddr`.
   - Kịch bản di chuyển đơn trục ($X$ đổi, $Y, Z$ tĩnh): Thành công $100\%$.
2. **Kết quả Bộ Kiểm thử Toàn diện**:
   - Lệnh thực thi: `.\build\bin\Release\AutoPOE2_Tests.exe`
   - Kết quả: `[RESULT] TẤT CẢ 771 KIỂM TRA ĐỀU ĐẠT (PASS)`.
