# 27. Hợp Đồng Companion Control Center ↔ Core (UI, Màu, Launch, Chống Drift)

- **Mã Tài Liệu**: `SPEC-DOC-27-CONTROL-CENTER-COMPANION-CONTRACT-20260910`
- **Mốc Thời Gian**: `10/09/2026 - 20:25:00`
- **Trạng Thái**: [HIỆN TRẠNG — khớp mã nguồn]
- **Phạm vi**: `run_full_system.bat`, `src/assistant_tool/control_center.py`, `src/assistant_tool/config.py`, `src/assistant_tool/ui_theme.py`, `src/assistant_tool/log_watcher.py`, `src/core/sensor/log_sensor.hpp/.cpp`

---

## 1. Bài toán drift (hiện trường 10/09/2026)

Tool treo `⏳ chờ xác thực vị trí bản đồ` trong khi screenshot HUD và `Client.txt` đã ghi **Hive Fortress** (18:29:57, PID 36104). Tài liệu cũ ghi `LatestClient.txt`, `run_full_system.bat` “chạy Core song song”, Overlay 510×700 lẫn với Control Center — ba nguồn chân lý lệch nhau.

Nguyên tắc của tài liệu này: **một trường dữ liệu = một nguồn ghi, nhiều nơi đọc**. Widget Python không được bịa tên map, máu, hay đường dẫn log.

---

## 2. Sơ đồ tương tác hệ thống (canonical launch)

```mermaid
sequenceDiagram
    actor User
    participant Bat as run_full_system.bat
    participant CC as Control Center (Python)
    participant LW as LogWatcher
    participant Core as AutoPOE2_Core.exe
    participant SHM as SHM 4MB seqlock
    participant Game as PathOfExile.exe

    User->>Bat: double-click (UAC Admin)
    Bat->>CC: python run_control_center.py
    CC->>LW: find_poe2_client_log() + seed 2MB đuôi Client.txt
    LW-->>CC: ZONE_CHANGE playable (bỏ Act/Atlas/(null))
    CC->>CC: lifecycle.has_real_map_log
    Note over Bat,Core: Bat KHÔNG spawn Core — tránh flicker taskkill
    User->>CC: F8 / F6
    CC->>Core: CoreController.start(build_cmd)
    Core->>Game: RPM + LogSensor ResolveLogPath(Client.txt)
    Core->>SHM: TelemetryPacket 120Hz write / Python 30Hz read
    CC->>SHM: area_name, HP/ES/Ward/Mana/Spirit, XYZ, radar
    Note over CC: HUD: SHM playable thắng; nếu rỗng dùng seed log
```

| Kênh | Tần số trong code | Ghi chú |
| :--- | :--- | :--- |
| Core hot loop | 120 Hz (~8.33 ms) | `src/core/main.cpp` |
| Python SHM poll | 30 Hz (`poll_interval=0.033`) | `CoreShmBridge` |
| Lifecycle Python | 1.0 s | `AutonomousLifecycleManager._lifecycle_loop` |
| LogWatcher tail | ~0.2 s khi có dòng mới | `log_watcher.py` |

---

## 3. Ma trận ánh xạ UI ↔ Core (SSoT)

| Widget Control Center | Nguồn chân lý | Cấm |
| :--- | :--- | :--- |
| `lbl_hp` | SHM `player.current_hp/max_hp` | OCR HUD khi RAM đã VERIFIED; số 778 |
| `lbl_vitals_es_ward` | SHM ES `+0x0C`, Ward `+0x20` trên LifeComponent | Gộp nhầm vào Mana |
| `lbl_mana` | SHM mana (Life `+0x420`) | Hiển thị ES trong nhãn Mana |
| `lbl_spirit` | SHM spirit (Life `+0x44C`) | Hardcode 8/138 |
| `lbl_xyz` | SHM posX/Y/Z | Coi (0,0,0) là “đang ở hideout” |
| `lbl_area` | SHM `area.areaName` nếu không phải UI scene; else `lifecycle.current_area_name` từ seed `Client.txt` | `Act 1–4`, `Atlas`, `(null)`, `[DNT]*` |
| `lbl_radar` | SHM entity counters | Fake 0 khi Core online |
| Nút World Map U | `has_real_map_log && is_in_town` | Gửi U trên Hive Fortress / map chiến đấu |
| F8 Start | `CoreController.build_cmd` → Core CLI | Spawn Core từ `.bat` mặc định |

Màu sắc: `Config.THEME` (`hp`, `es`, `ward`, `mana`, `spirit`, `start`, `alert_red`). `ui_theme.c(key)` là accessor duy nhất cho token Control Center.

Kích thước cửa sổ: `CONTROL_CENTER_WIDTH×HEIGHT = 980×680` (khác Overlay `510×700`). HUD strip ~72px; tính năng nằm trong 4 tab (Nhiệm vụ / Chiến đấu / Client / Tác vụ) để giảm diện tích cuộn.

| Token | Hex | Dùng cho |
| :--- | :--- | :--- |
| `hp` | `#f87171` | Máu / thanh HP (không CI) |
| `es` | `#38bdf8` | Energy Shield; thanh HP khi CI (`max_hp==1`) |
| `ward` | `#a78bfa` | Ward |
| `mana` | `#60a5fa` | Mana / thanh mana |
| `spirit` | `#c084fc` | Spirit / F10 |
| `start` | `#10b981` | F8 Bắt đầu |
| `alert_red` | `#ef4444` | Panic / HP lệch RAM |

---

## 4. `run_full_system.bat` — hành vi thật

1. Tự nâng Admin (RPM).
2. Tìm `bin\Release\AutoPOE2_Core.exe` (hoặc `bin\`, `build\bin\Release\`). Thiếu thì `cmake --build`.
3. Mở **Control Center**. Core chỉ chạy khi người dùng **F8**. Zoom/Fog không nằm trên F8 — dùng `run_visual_tool.bat` hoặc nút **Mở Visual Tool** (tab Client).
4. `--core` / `--standalone`: chạy Core độc lập, không mở GUI.
Toàn bộ hệ thống quy về một điểm khởi động duy nhất: `run_full_system.bat`.

---

## 5. Nhật ký game (SSoT hai tầng)

Ưu tiên `...\Path of Exile 2\logs\Client.txt` (đủ `Generating level` + `[SCENE] Set Source`). `LatestClient.txt` chỉ là fallback.

| Tầng | Hàm | Quy tắc |
| :--- | :--- | :--- |
| Python | `Config.find_poe2_client_log()` | `Client.txt` trước, `LatestClient.txt` sau, rồi PID → `logs\` |
| C++ | `LogSensor::kDefaultLogPath` + `ResolveLogPath` | Default = `...\logs\Client.txt`. Cùng thư mục: nếu basename là `Client.txt`/`LatestClient.txt` thì ưu tiên file `Client.txt` đang tồn tại. Fixture test (`test_*.log`) không bị đổi tên. |
| Seed giữa map | `LogWatcher.seed_latest_playable_zone` / `ReplayLatestPlayableZoneFromTail` | 2 MB đuôi; bỏ overlay Act/Atlas/(null)/[DNT] |

Tài liệu lịch sử (`Tham_Dinh/`, `docs/research/03`) có thể còn viết `LatestClient.txt` như quan sát cũ — **hành vi vận hành lấy hợp đồng này**.

---

## 6. Kiểm thử khóa hợp đồng

- `tests/test_control_center.py::test_hud_area_contract_and_world_map_gate`
- `tests/test_autonomous_lifecycle.py` (Hive Fortress không travel)
- `tests/test_core.cpp` Test 11b LogSensor seed Hive Fortress
- `tests/test_core.cpp` Test 11c `ResolveLogPath` ưu tiên `Client.txt`

---

## 7. Thiết kế tương tác (khoa học, chống drift)

Ba lớp đọc độc lập, một lớp ghi cho mỗi tín hiệu:

1. **Sinh tồn (hot path)** — Core 120 Hz đọc RAM LifeComponent. Python HUD chỉ hiển thị. Crash GUI không được kéo sập flask/logout.
2. **Khu vực (log path)** — `Client.txt` là sự kiện SCENE/Generating. Overlay UI (`Act 2`, `Atlas`) không phải map. SHM `area_name` playable thắng seed; seed thắng chuỗi rỗng.
3. **Hành vi U** — lệnh vật lý chỉ khi `has_real_map_log && is_in_town`. Map chiến đấu (Hive Fortress) không mở World Map.

Checklist khi sửa UI hoặc log (bắt buộc grep hai tầng):

- [ ] `run_full_system.bat` vẫn **không** spawn Core mặc định.
- [ ] Default log C++ và Python cùng ưu tiên `Client.txt`.
- [ ] Widget `lbl_hp` / `lbl_mana` / `lbl_area` không đổi tên; mana không ghép ES/Spirit.
- [ ] `lbl_vitals_es_ward` chỉ hiển thị SHM. ES `0/0` = Core chưa khóa Life+`0x0C` (không paint `2416`). SSoT: `vitals_fingerprint::kEsRel`. Cặp đảo `388/54` = sai Life, không phải “hết ES”.
- [ ] Token màu mới phải vào `Config.THEME` rồi `ui_theme.c`, không rải hex lệch HUD Core.
- [ ] Overlay 510×700 và Control Center 980×680 không dùng chung hằng kích thước.
- [ ] Test HUD + Test 11b/11c chạy trong phiên sửa, không dán số PASS cũ.
