#include "game_session.hpp"
#include <chrono>
#include <cstdio>
#include <iostream>
#include <thread>
#include <unordered_set>
#include "overlay.hpp"
#include "common/logger.hpp"
#include "memory/simulated_reader.hpp"
#include "memory/rpm_reader.hpp"
#include "memory/player_finder.hpp"
#include "memory/aob_scanner.hpp"
#include "memory/pe_fingerprint.hpp"
#include "memory/offset_registry.hpp"
#include "memory/offsets_store.hpp"
#include "navigation/terrain_grid.hpp"
#include "navigation/town_quest_engine.hpp"
#include "input/kmbox_net.hpp"
#include "input/kmbox_net.hpp"

namespace   {
    std::string HexStr(uint64_t v)   {
            char buf[32];
            std::snprintf(buf, sizeof(buf), "%llX", static_cast<unsigned long long>(v));
            return buf;
    }
    uint64_t CurrentTimeMs()   {
            return std::chrono::duration_cast<std::chrono::milliseconds>(        std::chrono::system_clock::now().time_since_epoch()).count();
    }
/* Dựng cảnh giả lập mặc định: quái đủ loại + boss + item + portal */
void BuildDefaultScenario(SimulatedMemoryReader& sim)   {
            using namespace game_layout;
            auto makeEntity = [](uint32_t id, uint16_t type, uint16_t rarity,                         float x, float y, uint32_t hp, const char* name,                         uint32_t flags)   {
                    EntityMemory e  {
            }
            ;
                    e.magic = kEntityMagic;
                    e.id = id;
                    e.type = type;
                    e.rarity = rarity;
                    e.posX = x;
             e.posY = y;
             e.posZ = 0.0f;
                    e.distanceToPlayer = 0.0f;
                    e.currentHP = hp;
             e.maxHP = hp;
                    e.staggerProgress = 0;
                    e.animationId = 0;
                    e.extraFlags = flags | kEntityFlagTargetable;
                    std::snprintf(e.name, sizeof(e.name), "%s", name);
                    return e;
        }
        ;
            sim.SpawnEntity(makeEntity(1, EntityTypeMonster, 0, 20, 15, 340, "Wild Rhoa", 0));
            sim.SpawnEntity(makeEntity(2, EntityTypeMonster, 1, -35, 40, 520, "Rhoa Graves Thug", 0));
            sim.SpawnEntity(makeEntity(3, EntityTypeMonster, 2, 60, -50, 1400, "Beach Map Boss Champion", 0));
            sim.SpawnEntity(makeEntity(4, EntityTypeMonster, 3, 10, -80, 6500, "The Broken Prince", kEntityFlagBoss));
            sim.SpawnEntity(makeEntity(5, EntityTypeMonster, 0, -70, -20, 280, "Sand Spitter", 0));
            sim.SpawnEntity(makeEntity(6, EntityTypeMonster, 1, 45, 25, 610, "Crab Swarm", 0));
            sim.SpawnEntity(makeEntity(7, EntityTypeMonster, 2, 200, 200, 2100, "Far Rare (out of range)", 0));
/* ngoài 150 đơn vị */
    sim.SpawnEntity(makeEntity(8, EntityTypeItemDrop, 3, 5, 5, 0, "Divine Orb", 0));
            sim.SpawnEntity(makeEntity(9, EntityTypeItemDrop, 1, -15, -5, 0, "Orb of Alchemy", 0));
}
}
/* namespace */

GameSession::~GameSession() = default;

void GameSession::NotifyRoll(uint64_t nowMs) {
    m_lastRollMs = (nowMs != 0) ? nowMs : CurrentTimeMs();
}

bool GameSession::Initialize(const std::string& backend, std::string& outStatus)   {
        auto tryRpm = [&](std::string& reason) -> bool   {
                std::wstring procName;
                const uint32_t pid = FindPoeProcessId(&procName);
                if (pid == 0)   {
             reason = "khong thay tien trinh PathOfExile.exe hoac PathOfExileSteam.exe";
             return false;
        }
                auto reader = std::make_unique<ReadProcessMemoryReader>();
                if (!reader->Attach(pid))   {
                        reason = "Access denied (game High Integrity - can chay Admin/DMA/Kernel)";
                        return false;
        }
                m_processPid = pid;
                m_processName = procName;
                m_reader = std::move(reader);
                return true;
    }
    ;
        auto trySim = [&](std::string& reason) -> bool   {
                auto reader = std::make_unique<SimulatedMemoryReader>();
                if (!reader->Initialize())   {
             reason = "khoi tao bo nho gia lap that bai";
             return false;
        }
                if (!reader->Attach(game_layout::kSimulatedPid))   {
                        reason = "attach game ao that bai";
             return false;
        }
                BuildDefaultScenario(*reader);
                m_simPtr = reader.get();
                m_reader = std::move(reader);
                return true;
    }
    ;
        bool ok = false;
        std::string rpmReason, simReason;
        if (backend == "sim" || backend == "simulated")   {
                ok = trySim(simReason);
                if (ok) outStatus = "[GameSession] Backend = SIMULATED (game ao 8MB, 10 entity mau).";
    }
     else if (backend == "rpm")   {
                ok = tryRpm(rpmReason);
                if (ok) outStatus = "[GameSession] Backend = RPM (ReadProcessMemory - game that).";
    }
     else   {
/* auto */
        ok = tryRpm(rpmReason);
                if (ok)   {
                        outStatus = "[GameSession] Backend = RPM (game that, PID da gan).";
        }
         else   {
                        ok = trySim(simReason);
                        if (ok) outStatus = "[GameSession] RPM khong kha dung (" + rpmReason                              + ") -> fallback sang SIMULATED.";
        }
    }
        if (ok)   {
                m_simulated = (m_simPtr != nullptr);
                EntityManagerConfig entCfg;
                if (!m_simulated)   {
                        entCfg.maxEntityDistance = 450.0f;
/* Bán kính hiển thị quái trên màn hình POE2 16:9 / 2K (~450 đơn vị) */
        }
                m_entities = std::make_unique<EntityManager>(*m_reader, entCfg);
                m_entities->SetSimulated(m_simulated);
                if (m_entityMapOffset != 0)   {
                        m_entities->SetEntityMapOffset(m_entityMapOffset);
        }
                if (m_inGameAddr != 0)   {
                        m_entities->SetInGameAddress(m_inGameAddr);
        }

                // Khởi tạo PointerChainResolver - Giải mã chuỗi con trỏ nhị phân tất định O(1)
                PointerChainConfig chainCfg{};
                chainCfg.isSimulated = m_simulated;
                chainCfg.staticRva = m_staticRva;
                chainCfg.mainBase = m_mainBase;
                chainCfg.entityMapOffset = m_entityMapOffset;
                m_pointerResolver = std::make_unique<PointerChainResolver>(*m_reader, chainCfg);
                ApplyOffsetRegistry();

                std::cout << outStatus << std::endl;
                std::cout << "[GameSession] EntityManager san sang (pointer chain: static -> InGameState -> +0x3E8 EntityMap)." << std::endl;
                std::cout << "[GameSession] PointerChainResolver san sang (deterministic O(1) traversal)." << std::endl;
    }
     else   {
                outStatus = "[GameSession] Loi: " + (rpmReason.empty() ? simReason : rpmReason)                  + (simReason.empty() ? "" : " | sim: " + simReason);
                std::cerr << outStatus << std::endl;
    }
        return ok;
}

// ==========================================================
// ApplyOffsetRegistry (INV-BUILD-01 - TASK-RE-PIPELINE-2026)
// Nạp tri thức RE từ OffsetRegistry (offsets_<BUILD_ID>.toml / offsets.toml)
// sinh bởi pipeline PyGhidra, đối chiếu build fingerprint của client đang cài:
//   - KHỚP  -> nạp static root + offset component, Tier 2b được phép chạy.
//   - LỆCH  -> arm build gate để VÔ HIỆU 8 static root RVA biên dịch sẵn
//               (bằng chứng thực địa: .data dịch -0x1000 giữa 2 build làm
//               8 RVA hardcode lệch hoàn toàn, 4 AOB pattern [MISS]).
// Chỉ chạy ở chế độ game thật (registry là tri thức về binary thật).
// ==========================================================
void GameSession::ApplyOffsetRegistry() {
    if (m_simulated || !m_pointerResolver) return;

    std::string exePath;
    if (!pe_fingerprint::LocatePoe2Executable(exePath)) {
        std::cout << "[OffsetRegistry] Khong dinh vi duoc PathOfExile.exe - giu hanh vi mac dinh." << std::endl;
        return;
    }
    pe_fingerprint::ClientBuildId live;
    std::string fpError;
    if (!pe_fingerprint::LoadPeHeaders(exePath, live, fpError)) {
        std::cout << "[OffsetRegistry] Khong doc duoc fingerprint client: " << fpError << std::endl;
        return;
    }

    // Xác định thư mục chứa exe Core (đồng bộ mẫu của SaveOffsets)
    wchar_t exePathW[MAX_PATH] = {};
    GetModuleFileNameW(nullptr, exePathW, MAX_PATH);
    char exeDir[MAX_PATH] = {};
    WideCharToMultiByte(CP_UTF8, 0, exePathW, -1, exeDir, sizeof(exeDir), nullptr, nullptr);
    std::string dir = exeDir;
    const auto slash = dir.find_last_of("\\/");
    const std::string baseDir = (slash != std::string::npos) ? dir.substr(0, slash) : ".";

    // Ưu tiên registry build-keyed, fallback về offsets.toml chung
    OffsetRegistry registry;
    std::string regPath = baseDir + "\\offsets_" + live.ShortId() + ".toml";
    if (!registry.Load(regPath)) {
        regPath = baseDir + "\\offsets.toml";
        if (!registry.Load(regPath)) {
            std::cout << "[OffsetRegistry] Chua co registry (offsets_" << live.ShortId()
                      << ".toml / offsets.toml) - bo qua, dung Tier mac dinh." << std::endl;
            return;
        }
    }

    const auto match = registry.CheckBuild(live);
    const bool usable = (match == OffsetRegistry::BuildMatch::Match);
    std::cout << "[OffsetRegistry] Build live=" << live.ShortId()
              << " | registry=" << registry.Path()
              << " | match=" << OffsetRegistry::ToString(match) << std::endl;

    // Arm build gate: registry lệch build -> Tier 2b (RVA biên dịch sẵn) bị tắt
    m_pointerResolver->SetBuildGate(true, usable);

    if (usable) {
        auto roots = registry.StaticRootCandidates();
        if (!roots.empty()) {
            m_pointerResolver->SetRegistryStaticRoots(roots);
            std::cout << "[OffsetRegistry] Da nap " << roots.size()
                      << " static root build-matched (Tier 2a)." << std::endl;
        }
        if (registry.EntityMapOffset() != 0) {
            m_entityMapOffset = registry.EntityMapOffset();
            m_pointerResolver->SetEntityMapOffset(m_entityMapOffset);
            std::cout << "[OffsetRegistry] entity_map_offset = 0x" << std::hex
                      << m_entityMapOffset << std::dec << std::endl;
        }
        // Không đè kết quả calibration live; chỉ vá chỗ còn trống
        if (m_statOff == 0 && registry.StatOffset() != 0) m_statOff = registry.StatOffset();
        if (m_posOff == 0 && registry.PosOffset() != 0) m_posOff = registry.PosOffset();
    } else {
        std::cout << "[OffsetRegistry] INV-BUILD-01: tri thuc offset thuoc build khac -> "
                     "BI CHAN khoi Tier 2a/2b, chi con Tier 3 (AOB registry) va Tier 4." << std::endl;
    }
}

bool GameSession::ResolveViaPointerChain() {    if (!m_pointerResolver || !m_reader) return false;

    m_pointerResolver->SetSimulated(m_simulated);
    m_pointerResolver->SetStaticRva(m_staticRva);
    if (m_mainBase != 0) m_pointerResolver->SetMainBase(m_mainBase);
    if (m_entityMapOffset != 0) m_pointerResolver->SetEntityMapOffset(m_entityMapOffset);

    ResolvedPlayerState state{};
    if (!m_pointerResolver->ResolvePlayer(state)) {
        return false;
    }

    if (state.lifeAddr == 0) return false;

    m_playerAddr = state.lifeAddr;
    if (state.posAddr != 0) {
        m_xyzAddr = state.posAddr;
        m_autoPosState = AutoPositionState::RESOLVED;
    }
    if (state.maxES > 0) {
        m_shieldAddr = (m_simulated ? (m_playerAddr + 20) : (m_shieldAddr ? m_shieldAddr : m_playerAddr + 12));
    }
    if (state.maxMana > 0) {
        m_manaAddr = (m_simulated ? (m_playerAddr + 24) : (m_manaAddr ? m_manaAddr : m_playerAddr + 24));
    }
    if (state.maxSpirit > 0) {
        m_spiritAddr = (m_simulated ? (m_playerAddr + 28) : (m_spiritAddr ? m_spiritAddr : m_playerAddr + 24));
    }
    if (state.maxWard > 0) {
        m_wardAddr = (m_simulated ? (m_playerAddr + 32) : (m_wardAddr ? m_wardAddr : m_playerAddr + 32));
    }
    m_hasData = true;

    if (m_entities) {
        m_entities->SetPlayerAddress(m_playerAddr);
        m_entities->SetPlayerXyzAddress(m_xyzAddr);
        if (m_pointerResolver->InGameAddress() != 0) {
            m_entities->SetInGameAddress(m_pointerResolver->InGameAddress());
        }
    }

    return true;
}

void GameSession::Update(TelemetryPacket& pkt)   {
        if (!m_entities) return;
        if (m_simPtr)   {
/* Tiến mô phỏng ~8ms mỗi tick (120Hz) */
        m_simPtr->Tick(0.008f);
    }
/* 1. Giải mã chuỗi con trỏ tất định O(1) qua PointerChainResolver */
    if (!m_simulated && m_pointerResolver && (m_playerAddr == 0 || m_xyzAddr == 0)) {
        ResolveViaPointerChain();
    }
/* 2. PlayerState đã xác định: ghi đè HP/maxHP bằng dữ liệu thật từ memory */
    if (m_playerAddr != 0 && m_reader)   {
                uint32_t hp = 0, mhp = 0;
                if (m_reader->ReadValue<uint32_t>(m_playerAddr, hp)            && m_reader->ReadValue<uint32_t>(m_playerAddr + 4, mhp)            && mhp > 0 && hp <= mhp)   {
                        static uint32_t s_lastKnownHP = 999999;
                        if (s_lastKnownHP == 0 && hp > 0) {
                            CoreLog("[GameSession] >> Phát hiện nhân vật hồi sinh! Làm mới thực thể và kích hoạt Auto-Scan... <<");
                            if (m_entities) m_entities->ClearEntities();
                            TriggerManualAutoScan();
                        }
                        s_lastKnownHP = hp;

                        static uint32_t s_lastKnownMaxHP = 0;
                        if (mhp == 1 && s_lastKnownMaxHP != 0) {
                            if (s_lastKnownMaxHP != 1) {
                                CoreLog("[GameSession] >> Phát hiện chuyển sang Chaos Inoculation (CI)! MaxHP: " + std::to_string(s_lastKnownMaxHP) + " -> 1 <<");
                                s_lastKnownMaxHP = 1;
                                m_expectedMaxHP = 1;
                            }
                        } else if (s_lastKnownMaxHP != 0 && mhp != s_lastKnownMaxHP && mhp >= 50 && mhp <= 25000) {
                            float changeRatio = std::abs((float)mhp - (float)s_lastKnownMaxHP) / (float)s_lastKnownMaxHP;
                            // Cho phép thay đổi lên tới 40% (lên cấp hoặc thay đổi nhiều đồ có thể tăng máu đáng kể)
                            // Hoặc nếu mhp khớp chính xác với m_expectedMaxHP từ Optical sensor
                            if (changeRatio <= 0.40f || (m_expectedMaxHP > 0 && mhp == m_expectedMaxHP)) {
                                CoreLog("[GameSession] >> Phát hiện MaxHP thay đổi hợp lệ (Lên cấp / Thay đồ): " + std::to_string(s_lastKnownMaxHP) + " -> " + std::to_string(mhp) + " <<");
                                s_lastKnownMaxHP = mhp;
                                if (m_expectedMaxHP > 0) {
                                    m_expectedMaxHP = mhp;
                                }
                            } else {
                                static uint64_t s_lastWarn = 0;
                                const uint64_t nowW = CurrentTimeMs();
                                if (nowW - s_lastWarn >= 2000) {
                                    s_lastWarn = nowW;
                                    CoreLog("[GameSession] ⚠️ CẢNH BÁO: MaxHP thay đổi bất thường (" + std::to_string(s_lastKnownMaxHP) + " -> " + std::to_string(mhp) + "). Bỏ qua để tránh dính stale pointer!");
                                }
                            }
                        } else if (s_lastKnownMaxHP == 0) {
                            s_lastKnownMaxHP = mhp;
                        }

                        pkt.player.currentHP = hp;
                        pkt.player.maxHP = mhp;
                        m_hasData = true;
                } else if (!m_simulated) {
                        static uint64_t s_lastStaleHpAttempt = 0;
                        const uint64_t now = CurrentTimeMs();
                        if (now - s_lastStaleHpAttempt >= 2000) {
                            s_lastStaleHpAttempt = now;
                            m_playerAddr = 0;
                            AutoDetectPlayerHP();
                        }
                }
    } else if (m_playerAddr == 0 && m_reader && !m_simulated) {
        static uint64_t s_lastZeroHpAttempt = 0;
        const uint64_t now = CurrentTimeMs();
        if (now - s_lastZeroHpAttempt >= 2000) {
            s_lastZeroHpAttempt = now;
            AutoDetectPlayerHP();
        }
    }

    // Ghi chú: Micro-Step cũ (tap W 45ms sleep / click chuột) đã bị loại bỏ vĩnh viễn theo
    // docs/development/23_deterministic_pointer_chain_and_obsolete_heuristic_elimination.md.
    // Tọa độ XYZ nay được giải mã trực tiếp từ PositionComponent với độ trễ 0ms.

    if (m_expectedMaxHP > 0 && pkt.player.maxHP == 0) {
        pkt.player.maxHP = m_expectedMaxHP;
        pkt.player.currentHP = m_expectedMaxHP;
    }
    
    pkt.player.playerAddr = m_playerAddr;
/* 2a. Đọc Energy Shield — RCA 2026-09-07 19:26:
    - ĐÃ XÓA nhánh flat +12/+16 (khóa nhầm cặp rác 0x7FFB/54 thành ES).
    - Đường đọc có BẤT BIẾN: chỉ nhận esMax trong [1, 20000] và es <= esMax
      (block ES giả đọc ngược 32763/54 sẽ bị từ chối, hiển thị 0/0 trung thực).
    - Địa chỉ stale (đọc rác liên tục) -> reset và dò lại. */
        uintptr_t shieldTarget = m_shieldAddr;
        if (shieldTarget == 0 && m_playerAddr != 0) {
            static uint64_t s_lastShieldAttempt = 0;
            const uint64_t nowShield = CurrentTimeMs();
            if (nowShield - s_lastShieldAttempt >= 3000) {
                s_lastShieldAttempt = nowShield;
                AutoDetectShield(m_expectedMaxES);
                shieldTarget = m_shieldAddr;
            }
        }
        if (shieldTarget != 0) {
            uint32_t es = 0, esMax = 0;
            if (m_reader->ReadValue<uint32_t>(shieldTarget, es) &&
                m_reader->ReadValue<uint32_t>(shieldTarget + 4, esMax) &&
                esMax > 0 && esMax <= 20000 && es <= esMax) {
                pkt.player.currentES = es;
                pkt.player.maxES = esMax;
                m_esStaleCount = 0;
            } else {
                pkt.player.currentES = 0;
                pkt.player.maxES = 0;
                // esMax đọc ra > 0 nhưng vi phạm bất biến (đảo ngược / rác) -> địa chỉ stale
                if (!m_simulated && esMax > 0 && (es > esMax || esMax > 20000)) {
                    if (++m_esStaleCount >= 60) {
                        m_esStaleCount = 0;
                        m_shieldAddr = 0;
                        CoreLog("[GameSession] ES stale/inverted (vi phạm cur<=max) -> Reset và dò lại Energy Shield.");
                        AutoDetectShield(m_expectedMaxES);
                    }
                }
            }
        } else {
            pkt.player.currentES = 0;
            pkt.player.maxES = 0;
        }
/* 2b. Đọc Spirit — RCA 2026-09-07 19:26: flat +36 trên client thật hiện là
    thành phần của CON TRỎ (high word 0x269) nên bị loại bỏ hoàn toàn.
    - Sim: PlayerStateMemory đặt Spirit tại HPbase + 24 (tắc chắc).
    - Thật: chỉ dùng m_spiritAddr; nếu chưa có -> AutoDetectSpirit scan
      (chạy SAU khi Mana đã khóa để tránh nhầm pool Mana). */
        if (m_spiritAddr != 0) {
            uint32_t sp = 0, spMax = 0;
            if (m_reader->ReadValue<uint32_t>(m_spiritAddr, sp) &&
                m_reader->ReadValue<uint32_t>(m_spiritAddr + 4, spMax) &&
                spMax > 0 && spMax <= 10000 && sp <= spMax) {
                pkt.player.currentSpirit = sp;
                pkt.player.maxSpirit = spMax;
            } else {
                pkt.player.currentSpirit = 0;
                pkt.player.maxSpirit = 0;
                if (!m_simulated) {
                    m_spiritAddr = 0; // stale -> dò lại ở nhánh dưới
                }
            }
        } else {
            pkt.player.currentSpirit = 0;
            pkt.player.maxSpirit = 0;
            if (m_simulated && m_playerAddr != 0) {
                uint32_t sp = 0, spMax = 0;
                if (m_reader->ReadValue<uint32_t>(m_playerAddr + 24, sp) &&
                    m_reader->ReadValue<uint32_t>(m_playerAddr + 28, spMax) &&
                    spMax > 0 && spMax <= 10000 && sp <= spMax) {
                    pkt.player.currentSpirit = sp;
                    pkt.player.maxSpirit = spMax;
                }
            } else if (m_manaAddr != 0) {
                const uint64_t nowSpirit = CurrentTimeMs();
                if (nowSpirit - m_lastSpiritAttemptMs >= 3000) {
                    m_lastSpiritAttemptMs = nowSpirit;
                    AutoDetectSpirit(0);
                }
            }
        }
/* 2bb. Đọc Ward (từ m_wardAddr hoặc fallback) */
        if (m_wardAddr != 0) {
            uint32_t ward = 0, wardMax = 0;
            if (m_reader->ReadValue<uint32_t>(m_wardAddr, ward) &&
                m_reader->ReadValue<uint32_t>(m_wardAddr + 4, wardMax) &&
                wardMax > 0 && wardMax <= 20000 && ward <= wardMax) {
                pkt.player.currentWard = ward;
                pkt.player.maxWard = wardMax;
            } else {
                pkt.player.currentWard = 0;
                pkt.player.maxWard = 0;
                if (!m_simulated) {
                    m_wardAddr = 0;
                }
            }
        } else {
            pkt.player.currentWard = 0;
            pkt.player.maxWard = 0;
            if (m_playerAddr != 0) {
                if (m_simulated) {
                    uint32_t ward = 0, wardMax = 0;
                    if (m_reader->ReadValue<uint32_t>(m_playerAddr + 32, ward) &&
                        m_reader->ReadValue<uint32_t>(m_playerAddr + 36, wardMax) &&
                        wardMax > 0 && wardMax <= 20000 && ward <= wardMax) {
                        pkt.player.currentWard = ward;
                        pkt.player.maxWard = wardMax;
                    }
                } else {
                    static uint64_t lastWardDetectAttempt = 0;
                    const uint64_t nowWard = CurrentTimeMs();
                    if (nowWard - lastWardDetectAttempt >= 2000) {
                        lastWardDetectAttempt = nowWard;
                        AutoDetectWard(m_expectedMaxWard);
                    }
                }
            }
        }
/* 2c. Đọc Mana (ưu tiên flat offset +0x18, pointer +0x58, hoặc m_manaAddr) */
        // Khóa chặn xung đột: Nếu m_manaAddr vô tình trùng m_wardAddr hoặc offset +32 của Ward
        if (m_manaAddr != 0 && (m_manaAddr == m_wardAddr || (m_playerAddr != 0 && m_manaAddr == m_playerAddr + 32))) {
            CoreLog("[GameSession] ⚠️ Phát hiện m_manaAddr chiếm dụng nhầm WardAddress! Reset m_manaAddr để dò lại!");
            m_manaAddr = 0;
        }
        uintptr_t manaTarget = m_manaAddr;
        if (manaTarget == 0 && m_playerAddr != 0) {
            // Bước 1: Thử đọc trực tiếp từ layout chuẩn LifeComponent POE2 (+0x18 tức +24 bytes)
            uint32_t fCur = 0, fMax = 0;
            if (m_reader->ReadValue<uint32_t>(m_playerAddr + 24, fCur) &&
                m_reader->ReadValue<uint32_t>(m_playerAddr + 28, fMax) &&
                fMax >= 15 && fMax <= 50000 && fCur <= fMax && fMax != pkt.player.maxHP &&
                (m_expectedMaxWard == 0 || fMax != m_expectedMaxWard) &&
                (m_expectedMaxES == 0 || fMax != m_expectedMaxES)) {
                m_manaAddr = m_playerAddr + 24;
                manaTarget = m_manaAddr;
                CoreLog("[GameSession] >> Tự động khóa Mana tại LifeComponent flat offset +0x18 (+24) <<");
            } else {
                // Bước 2: Thử đọc qua con trỏ VitalStruct tại +0x58 (+88 bytes)
                uint64_t compPtr = 0;
                if (m_reader->ReadValue<uint64_t>(m_playerAddr + 88, compPtr) && compPtr >= 0x10000 && compPtr <= 0x7FFFFFFEFFFF) {
                    uint32_t pCur = 0, pMax = 0;
                    if (m_reader->ReadValue<uint32_t>(compPtr, pCur) &&
                        m_reader->ReadValue<uint32_t>(compPtr + 4, pMax) &&
                        pMax >= 15 && pMax <= 50000 && pCur <= pMax && pMax != pkt.player.maxHP) {
                        m_manaAddr = static_cast<uintptr_t>(compPtr);
                        manaTarget = m_manaAddr;
                        CoreLog("[GameSession] >> Tự động khóa Mana qua con trỏ VitalStruct tại +0x58 <<");
                    }
                }
            }

            if (manaTarget == 0) {
                static uint64_t lastManaDetectAttempt = 0;
                const uint64_t now = CurrentTimeMs();
                if (now - lastManaDetectAttempt >= 2000) {
                    lastManaDetectAttempt = now;
                    AutoDetectManaPool(m_expectedMaxMana);
                    manaTarget = m_manaAddr;
                }
            }
        }
        if (manaTarget != 0) {
            uint32_t val1 = 0, val2 = 0;
            bool valid = false;
            if (m_reader->ReadValue<uint32_t>(manaTarget, val1) &&
                m_reader->ReadValue<uint32_t>(manaTarget + 4, val2)) {
                uint32_t cur = val1;
                uint32_t mmax = val2;
                // Nhận diện thứ tự {Max, Current} (chuẩn VitalStruct POE2)
                if (val1 > val2 && val1 >= 30 && val1 <= 50000 && val2 <= val1 * 2) {
                    mmax = val1;
                    cur = val2;
                }
                if (mmax >= 15 && mmax <= 50000 && cur <= mmax * 2 && !(cur == 0 && mmax == 1032)) {
                    pkt.player.currentMana = cur;
                    pkt.player.maxMana = mmax;
                    valid = true;
                }
            }
            if (!valid) {
                /* Nếu địa chỉ hiện tại đọc ra 0/0 hoặc dữ liệu rác 0/1032, định kỳ kích hoạt dò lại */
                static uint64_t lastInvalidManaLog = 0;
                const uint64_t now = CurrentTimeMs();
                if (now - lastInvalidManaLog >= 3000) {
                    lastInvalidManaLog = now;
                    CoreLog("[GameSession] mana_addr không còn đọc được giá trị hợp lệ -> Dò lại!");
                    m_manaAddr = 0;
                    AutoDetectManaPool(m_expectedMaxMana);
                }
            }
        }
/* 3. Tọa độ XYZ đã calibrate: đọc 3 float liên tiếp mỗi tick */
    if (m_xyzAddr != 0 && m_reader)   {
                float x = 0, y = 0, z = 0;
                if (m_reader->ReadValue<float>(m_xyzAddr, x)            && m_reader->ReadValue<float>(m_xyzAddr + 4, y)            && m_reader->ReadValue<float>(m_xyzAddr + 8, z))   {
                        if (PlayerFinder::IsValidXYZ(x, y, z))   {
                                pkt.player.posX = x;
                                pkt.player.posY = y;
                                pkt.player.posZ = z;
                                m_hasData = true;
            }
             else if (!m_simulated)   {
                                static uint32_t invalidXyzCount = 0;
                                if (++invalidXyzCount >= 60)   {
                                        invalidXyzCount = 0;
                                        m_xyzAddr = 0;
                                        TriggerManualAutoScan();
                }
            }
        }
         else if (!m_simulated)   {
                        static uint32_t failedXyzCount = 0;
                        if (++failedXyzCount >= 60)   {
                                failedXyzCount = 0;
                                m_xyzAddr = 0;
                                TriggerManualAutoScan();
            }
        }
    }
/* Cập nhật vị trí người chơi cho EntityManager tính khoảng cách thực tế */
    if (pkt.player.posX != 0.0f || pkt.player.posY != 0.0f)   {
                m_entities->SetPlayerPosition(pkt.player.posX, pkt.player.posY, pkt.player.posZ);
    }
        if (m_playerAddr != 0)   {
                m_entities->SetPlayerAddress(m_playerAddr);
    }
        if (m_xyzAddr != 0)   {
                m_entities->SetPlayerXyzAddress(m_xyzAddr);
    }
        if (m_inGameAddr != 0)   {
                m_entities->SetInGameAddress(m_inGameAddr);
    }
        if (m_entityMapOffset != 0)   {
                m_entities->SetEntityMapOffset(m_entityMapOffset);
    }
        bool entitiesOk = m_entities->Update();
        if (entitiesOk)   {
                m_entities->FillTelemetry(pkt);
                m_entityCount = pkt.entityCount;
                m_filteredCount = m_entities->TotalEntitiesFiltered();
                m_hasData = true;
/* Tự động lưu offset EntityMap nếu vừa tự động dò ra trên game thật */
        if (m_entityMapOffset == 0 && m_entities->EntityMapOffset() != 0)   {
                        m_entityMapOffset = m_entities->EntityMapOffset();
                        SaveOffsets();
        }
    } else {
        pkt.entityCount = 0;
        m_entityCount = 0;
    }

    // Tính toán movementFlags thực tế của người chơi mỗi tick:
    // Lưu tọa độ frame trước lastPosX, lastPosY. Tính delta = sqrt(dx^2 + dy^2).
    float delta = 0.0f;
    if (pkt.player.posX != 0.0f || pkt.player.posY != 0.0f) {
        if (m_lastPosX == 0.0f && m_lastPosY == 0.0f) {
            m_lastPosX = pkt.player.posX;
            m_lastPosY = pkt.player.posY;
            delta = 0.0f;
        } else {
            const float dx = pkt.player.posX - m_lastPosX;
            const float dy = pkt.player.posY - m_lastPosY;
            delta = std::sqrt(dx * dx + dy * dy);
            m_lastPosX = pkt.player.posX;
            m_lastPosY = pkt.player.posY;
        }
    }

    if (delta > 0.05f) {
        m_inGracePeriod = false; // Phá vỡ Grace Period ngay khi có bước di chuyển đầu tiên
    }

    // Phân loại cờ trạng thái:
    // - Nếu pkt.player.currentHP == 0: movementFlags = 8 (Bit 3: Dead).
    // - Ngược lại nếu đang trong Grace Period (chưa bước di chuyển đầu tiên): movementFlags = 16 (Bit 4: Grace Period).
    // - Ngược lại nếu delta > 0.05f: movementFlags = 1 (Bit 0: Moving).
    // - Ngược lại: movementFlags = 4 (Bit 2: Idle).
    // - Nếu vừa lướt né (Reflex roll active < 350ms): movementFlags |= 2 (Bit 1: Rolling).
    uint32_t movementFlags = 0;
    if (pkt.player.currentHP == 0) {
        movementFlags = 8; // Bit 3: Dead
    } else if (m_inGracePeriod) {
        movementFlags = 16; // Bit 4: Grace Period
    } else if (delta > 0.05f) {
        movementFlags = 1; // Bit 0: Moving
    } else {
        movementFlags = 4; // Bit 2: Idle
    }

    const uint64_t nowMs = CurrentTimeMs();
    if (m_lastRollMs > 0 && nowMs >= m_lastRollMs && (nowMs - m_lastRollMs < 350)) {
        movementFlags |= 2; // Bit 1: Rolling
    }
    pkt.player.movementFlags = movementFlags;


}
/* ---- Entity Array calibration (port từ entscan thực địa) ---- */
bool GameSession::CalibrateEntities()   {
        if (!m_reader || m_playerAddr == 0) return false;
        if (!m_finder) m_finder = std::make_unique<PlayerFinder>(*m_reader);
        std::cout << "[Calibrate] Tim Entity Pointer Array tu PlayerState..." << std::endl;
        PlayerFinder::EntityArrayInfo info;
        if (!m_finder->FindEntityArray(m_playerAddr, info))   {
                std::cout << "[Calibrate] Khong tim thay entity array (HP hop le < 8)." << std::endl;
                CoreLog("[Entities] FAILED - plausible=" + std::to_string(info.plausible));
                return false;
    }
        m_entitySlot = info.slotAddr;
        m_statOff = info.statOff;
        const std::string l = "[Entities] ARRAY OK! slot=0x" + HexStr(info.slotAddr)        + " statOff=0x" + HexStr(info.statOff)        + " | " + std::to_string(info.plausible) + "/"        + std::to_string(info.totalRead) + " entity song";
        std::cout << l << std::endl;
        CoreLog(l);
/* Log 10 entity đầu tiên kèm HP */
    for (size_t i = 0;
     i < info.hps.size() && i < 10;
     ++i)   {
                const std::string el = "[Entities]   [" + std::to_string(info.hps[i].first)            + "] ent=0x" + HexStr(info.slots[i].second) + " HP="            + std::to_string(info.hps[i].second);
                std::cout << el << std::endl;
                CoreLog(el);
    }
        return true;
}
/* ---- Khám phá offset XYZ trong entity object (cần user di chuyển) ---- */
bool GameSession::DiscoverEntityPosOffset()   {
        if (!m_reader || m_entitySlot == 0) return false;
        if (!m_finder) m_finder = std::make_unique<PlayerFinder>(*m_reader);
        uint64_t entBase = 0;
        if (!m_reader->ReadValue<uint64_t>(m_entitySlot, entBase) || entBase == 0)   {
                std::cout << "[Calibrate] Khong doc duoc entity base cua player." << std::endl;
                return false;
    }
        std::cout << "[Calibrate] Quet float trong player entity object (4KB)..." << std::endl;
        overlay::SetLines(  {
        "XYZ: DUNG YEN - dang quet entity object..."
    }
    );
        m_finder->ScanFloats(static_cast<uintptr_t>(entBase), 4096, 0.5f, 5000.0f);
        Beep(880, 150);
     Beep(880, 150);
        overlay::SetLines(  {
        "XYZ: DI CHUYEN ~15s!", "Core tu phat hien offset trong entity"
    }
    );
        std::cout << "[Calibrate] DI CHUYEN ~15s de tim offset XYZ trong entity..." << std::endl;
        m_finder->WaitForMoved(1.0f, 30, 2);
        Beep(1400, 400);
        overlay::SetLines(  {
        "XYZ: DUNG YEN 5s..."
    }
    );
        m_finder->WaitForStable(0.05f, 3, 20);
        Beep(880, 150);
     Beep(880, 150);
        overlay::SetLines(  {
        "XYZ: DI CHUYEN LAN CUOI 10s..."
    }
    );
        m_finder->WaitForMoved(0.5f, 30, 2);
        Beep(1400, 400);
        const size_t triples = m_finder->FindXYZTriples();
        std::cout << "[Calibrate] XYZ triples trong entity: " << triples << std::endl;
        CoreLog("[Entities] pos triples: " + std::to_string(triples));
        for (uintptr_t t : m_finder->XYZAddresses())   {
                if (t >= entBase && t - entBase <= 4096)   {
                        m_posOff = t - entBase;
                        float x = 0, y = 0, z = 0;
                        m_finder->ReadPositionAt(t, x, y, z);
                        const std::string l = "[Entities] posOff = 0x" + HexStr(m_posOff)                + " (X=" + std::to_string(x) + ", Y=" + std::to_string(y)                + ", Z=" + std::to_string(z) + ") - ENTITY POS OK!";
                        std::cout << l << std::endl;
                        CoreLog(l);
                        return true;
        }
    }
        CoreLog("[Entities] Khong tim thay posOff trong entity object.");
        std::cout << "[Calibrate] Khong tim thay pos offset - XYZ co the o component khac." << std::endl;
        return false;
}
/* ---- Auto-calibration PlayerState (phiên bản không tương tác) ---- */
bool GameSession::Calibrate(uint32_t maxHP, uint32_t newHP)   {
        if (!m_reader) return false;
        if (!m_finder) m_finder = std::make_unique<PlayerFinder>(*m_reader);
        const size_t found = m_finder->Scan(maxHP);
        std::cout << "[Calibrate] Quet {HP," << maxHP << "," << maxHP              << "}: " << found << " block" << std::endl;
        if (found == 0) return false;
        const size_t kept = m_finder->Rescan(newHP);
        std::cout << "[Calibrate] Sau khi loc HP=" << newHP << ": "              << kept << " block" << std::endl;
        if (kept == 0) return false;
        uint32_t hp = 0, mhp = 0;
        if (!m_finder->ReadStats(hp, mhp)) return false;
        m_playerAddr = m_finder->Candidates().front();
        std::cout << "[Calibrate] PlayerState = 0x" << std::hex << m_playerAddr              << std::dec << " (HP=" << hp << "/" << mhp << ")" << std::endl;
        return true;
}
/* ---- Calibration tọa độ XYZ (float differential, tự động theo prompt) ---- */
bool GameSession::CalibrateInteractive()   {
        if (!m_reader) return false;
        if (!m_finder) m_finder = std::make_unique<PlayerFinder>(*m_reader);
        overlay::SetLines(  {
        "=== HP CALIBRATION ===", "Alt-tab console -> nhap maxHP"
    }
    );
        std::cout << "[Calibrate] Nhap maxHP cua nhan vat (vd: 42): " << std::flush;
        uint32_t maxHP = 0;
        if (!(std::cin >> maxHP) || maxHP == 0) return false;
        overlay::SetLines(  {
        "DANG QUET RAM...", "~2-3 PHUT - doi trong console"
    }
    );
        const size_t found = m_finder->Scan(maxHP);
        std::cout << "[Calibrate] Quet xong: " << found << " block co pattern {HP,"              << maxHP << "," << maxHP << "}" << std::endl;
        if (found == 0)   {
                std::cout << "[Calibrate] Khong thay block nao - kiem tra lai maxHP." << std::endl;
                return false;
    }
        std::cout << "[Calibrate] Bay gio hay DE NHAN VAT MAT MAU (cho quai dau / nhay vuc)."              << "\n[Calibrate] HOAC nhap 0 neu dang o Hideout/Town de BO QUA HP, sang thang XYZ: " << std::flush;
        uint32_t newHP = 0;
        if (!(std::cin >> newHP)) newHP = 0;
        if (newHP == 0 || newHP >= maxHP)   {
                std::cout << "[Calibrate] Nhan vat chua mat mau (dang o Hideout/Town an toan)." << std::endl;
                std::cout << "[Calibrate] Su dung stat block ung vien dau tien lam moc quet XYZ..." << std::endl;
                m_playerAddr = m_finder->Candidates().front();
                uint32_t hp = 0, mhp = 0;
                m_finder->ReadStats(hp, mhp);
                std::cout << "[Calibrate] PlayerState (tam thoi) = 0x" << std::hex << m_playerAddr                  << std::dec << " (HP=" << hp << "/" << mhp << ")" << std::endl;
                overlay::SetLines(  {
            "BO QUA HP", "CHUYEN SANG XYZ CALIBRATION..."
        }
        );
    }
     else   {
                const size_t kept = m_finder->Rescan(newHP);
                std::cout << "[Calibrate] Sau loc: " << kept << " block" << std::endl;
                if (kept == 0)   {
                        std::cout << "[Calibrate] Khong con ung vien - dung candidate dau tien." << std::endl;
                        m_playerAddr = m_finder->Candidates().front();
        }
         else   {
                        uint32_t hp = 0, mhp = 0;
                        if (m_finder->ReadStats(hp, mhp))   {
                                m_playerAddr = m_finder->Candidates().front();
                                std::cout << "[Calibrate] PlayerState = 0x" << std::hex << m_playerAddr                          << std::dec << " (HP=" << hp << "/" << mhp << ") - CALIBRATED!" << std::endl;
                                overlay::SetLines(  {
                    "HP CALIBRATED!", "HP=" + std::to_string(hp) + "/" + std::to_string(mhp)
                }
                );
            }
        }
    }
/* ---- Mana calibration (tu chon - nhap 0 de bo qua) ---- */
    overlay::SetLines(  {
        "=== MANA CALIBRATION ===", "Nhap maxMana (0 = bo qua)"
    }
    );
        std::cout << "[Calibrate] Nhap maxMana (0 = bo qua): " << std::flush;
        uint32_t maxMana = 0;
        if ((std::cin >> maxMana) && maxMana > 0)   {
                const size_t mFound = m_finder->Scan(maxMana);
                std::cout << "[Calibrate] Mana: " << mFound << " block {mana,"                  << maxMana << "," << maxMana << "}" << std::endl;
                overlay::SetLines(  {
            "DUNG SKILL de ton mana!", "Alt-tab console -> nhap mana hien tai"
        }
        );
                if (mFound > 0)   {
                        std::cout << "[Calibrate] DUNG KY NANG de ton mana, roi nhap mana hien tai: "                      << std::flush;
                        uint32_t curMana = 0;
                        if ((std::cin >> curMana) && m_finder->Rescan(curMana) >= 1)   {
                                m_manaAddr = m_finder->Candidates().front();
                                uint32_t mv = 0, mm = 0;
                                m_finder->ReadStats(mv, mm);
                                std::cout << "[Calibrate] Mana block = 0x" << std::hex << m_manaAddr                          << std::dec << " (Mana=" << mv << "/" << mm << ") - OK!"                          << std::endl;
            }
             else   {
                                std::cout << "[Calibrate] Mana calibrate that bai - bo qua." << std::endl;
            }
        }
    }
        return true;
}
bool GameSession::CalibratePositionInteractive()   {
        if (!m_reader) return false;
        if (!m_finder) m_finder = std::make_unique<PlayerFinder>(*m_reader);
        constexpr size_t kWindow = 64 * 1024 * 1024;
     /*  64MB quanh PlayerState    std::cout << "[Calibrate] DUNG YEN - dang quet float trong bo nho game..." << std::endl */ ;
        overlay::SetLines(  {
                ">> XYZ CALIBRATION <<",        "DUNG YEN - dang quet float...",        "(khoang 5-10s)"
    }
    );
        size_t n = m_finder->ScanFloats(m_playerAddr, kWindow, 0.5f, 5000.0f);
        std::cout << "[Calibrate] " << n << " float ung vien." << std::endl;
        CoreLog("[XYZ] init: " + std::to_string(n));
/* Vòng 1: TỰ ĐỘNG phát hiện di chuyển (hiển thị trực quan trên Overlay) */
    std::cout << "[Calibrate] >>> BUOC 1: DI CHUYEN NHAN VAT QUANH HIDEOUT <<<" << std::endl;
        overlay::SetLines(  {
                ">> BUOC 1: HAY CHAY QUANH <<",        "Di chuyen lien tuc trong Hideout...",        "Doi den khi overlay hien 'DUNG YEN'"
    }
    );
        CoreLog("[XYZ] cho di chuyen (toida 60s)...");
        n = m_finder->WaitForMoved(1.0f, 60, 10);
        std::cout << "[Calibrate] Phat hien di chuyen: " << n << " ung vien" << std::endl;
        CoreLog("[XYZ] moved: " + std::to_string(n));
        if (n == 0)   {
                CoreLog("[XYZ] FAILED - khong phat hien di chuyen");
                overlay::SetLines(  {
            ">> FAILED: KHONG THAY DI CHUYEN <<", "Thu lai bang cach chay rong hon..."
        }
        );
                std::cout << "[Calibrate] Khong phat hien di chuyen trong 60s - chay lai calibration." << std::endl;
                return false;
    }
/* Vòng 2: ĐỨNG YÊN (chỉ thị trực quan trên Overlay) */
    overlay::SetLines(  {
                ">> BUOC 2: DUNG YEN TAI CHO! <<",        "Tha tay khoi chuot va ban phim...",        "Doi he thong on dinh vi tri..."
    }
    );
        std::cout << "[Calibrate] >>> BUOC 2: DUNG YEN TAI CHO (buong chuot va phim) <<<" << std::endl;
        n = m_finder->WaitForStable(0.05f, 3, 45);
        std::cout << "[Calibrate] Sau 'still': " << n << " ung vien" << std::endl;
        CoreLog("[XYZ] still: " + std::to_string(n));
        if (n == 0)   {
                CoreLog("[XYZ] FAILED - still = 0");
                overlay::SetLines(  {
            ">> FAILED: KHONG ON DINH <<"
        }
        );
                return false;
    }
/* Vòng 3: DI CHUYỂN LẦN CUỐI (chỉ giữ tọa độ) */
    overlay::SetLines(  {
                ">> BUOC 3: BUOC THEM VAI BUOC <<",        "Click chuot di chuyen 3-5 buoc...",        "Doi he thong bat toa do XYZ..."
    }
    );
        std::cout << "[Calibrate] >>> BUOC 3: DI CHUYEN THEM VAI BUOC CHAN <<<" << std::endl;
        n = m_finder->WaitForMoved(0.5f, 60, 5);
        std::cout << "[Calibrate] Di chuyen lan cuoi: " << n << " ung vien" << std::endl;
        CoreLog("[XYZ] moved cuoi: " + std::to_string(n));
        CoreLog("[XYZ] sau moved lan 2: " + std::to_string(n));
        const size_t triples = m_finder->FindXYZTriples();
        std::cout << "[Calibrate] XYZ triples: " << triples << std::endl;
        CoreLog("[XYZ] triples: " + std::to_string(triples));
        for (const auto& t : m_finder->XYZAddresses()) {
            float fx = 0, fy = 0, fz = 0;
            if (m_finder->ReadPosition(fx, fy, fz)) {
                /* Đọc giá trị từng triple để log */
                float a[3] = { 0, 0, 0 };
                m_reader->ReadValue<float>(t, a[0]);
                m_reader->ReadValue<float>(t + 4, a[1]);
                m_reader->ReadValue<float>(t + 8, a[2]);
                const std::string tl = "[XYZ] 0x" + HexStr(t) + " = (" + std::to_string(a[0]) + ", " + std::to_string(a[1]) + ", " + std::to_string(a[2]) + ")";
                CoreLog(tl);
            }
        }
        if (triples == 0)   {
                std::cout << "[Calibrate] Khong tim thay XYZ - thu lai hoac mo rong cua so." << std::endl;
                return false;
    }
        float x = 0, y = 0, z = 0;
        if (!m_finder->ReadPosition(x, y, z)) return false;
        m_xyzAddr = m_finder->XYZAddresses().front();
        std::cout << "[Calibrate] XYZ = 0x" << std::hex << m_xyzAddr << std::dec              << " (" << x << ", " << y << ", " << z << ") - POSITION CALIBRATED!" << std::endl;
        overlay::SetLines(  {
                ">> XYZ CALIBRATED! <<",        "X=" + std::to_string(x),        "Y=" + std::to_string(y),        "Z=" + std::to_string(z)
    }
    );
/* Tự động lưu xyz_addr vào offsets.toml */
    wchar_t exePathW[MAX_PATH] =   {
    }
    ;
        GetModuleFileNameW(nullptr, exePathW, MAX_PATH);
        char exeDir[MAX_PATH] =   {
    }
    ;
        WideCharToMultiByte(CP_UTF8, 0, exePathW, -1, exeDir, sizeof(exeDir), nullptr, nullptr);
        std::string dir = exeDir;
        auto slash = dir.find_last_of("\\/");
        std::string baseDir = (slash != std::string::npos) ? dir.substr(0, slash) : ".";
        std::string tomlPath = baseDir + "\\offsets.toml";
        OffsetsStore store;
        store.Load(tomlPath);
        /* INV-OFFSET-01 (Zero Absolute Heap Addresses):
           TUYET DOI khong ghi dia chi heap tuyet doi (xyz_addr) xuong offsets.toml.
           ASLR cap phat lai heap sau moi phien chay -> gia tri do luon stale va
           tung gay su co "o nhiem cache vital 778/778".
           Thay vao do, suy ra OFFSET TUONG DOI cua XYZ so voi entity base
           (pos_offset) - gia tri ben vung qua cac phien chay va qua cac map.
           Tham chieu: docs/development/06_api_and_ipc_specification.md muc 6 va
           docs/development/25_static_reverse_engineering_pipeline_and_offset_registry.md */
        uintptr_t posOffset = 0;
        if (m_playerAddr != 0 && m_xyzAddr >= m_playerAddr &&
            (m_xyzAddr - m_playerAddr) <= 4096) {
            posOffset = m_xyzAddr - m_playerAddr;
        } else if (m_posOff != 0) {
            posOffset = m_posOff;
        }

        if (posOffset != 0) {
            m_posOff = posOffset;
            if (store.Set("pos_offset", posOffset)) {
                store.Save(tomlPath);
                std::cout << "[Calibrate] Da luu pos_offset = 0x" << std::hex << posOffset
                          << std::dec << " (offset tuong doi - KHONG luu dia chi heap) vao "
                          << tomlPath << std::endl;
            }
        } else {
            std::cout << "[Calibrate] Khong suy duoc pos_offset tuong doi - bo qua persist "
                         "(INV-OFFSET-01 cam luu dia chi heap tuyet doi)." << std::endl;
        }
        return true;
}
void GameSession::OnInstanceTransition() {
    m_instanceTransitionPending = true;
    const std::string msg = "[GameSession] OnInstanceTransition: Phát hiện tín hiệu chuyển instance/arena -> Sẵn sàng reset và re-sync.";
    std::cout << msg << std::endl;
    CoreLog(msg);
}

void GameSession::OnZoneSceneChanged(const std::string& areaName) {
    if (IsUIOverlayScene(areaName)) {
        const std::string dbgMsg = "[GameSession] OnZoneSceneChanged: Bỏ qua chuyển cảnh UI Overlay: '"
            + areaName + "' (hiện tại: '" + m_currentSceneName + "'). Bỏ qua reset m_playerAddr và hủy m_townQuestEngine.";
        std::cout << dbgMsg << std::endl;
        CoreLog(dbgMsg);
        return;
    }

    if (areaName == m_currentSceneName && !m_instanceTransitionPending) {
        const std::string dbgMsg = "[GameSession] OnZoneSceneChanged: Bỏ qua chuyển cảnh UI Overlay hoặc trùng map: '"
            + areaName + "' (hiện tại: '" + m_currentSceneName + "'). Bỏ qua reset m_playerAddr và hủy m_townQuestEngine.";
        std::cout << dbgMsg << std::endl;
        CoreLog(dbgMsg);
        return;
    }

    m_instanceTransitionPending = false;
    m_currentSceneName = areaName;
    m_inGracePeriod = true;
    m_lastPosX = 0.0f;
    m_lastPosY = 0.0f;
    m_autoPosState = AutoPositionState::WAITING_ZONE_LOAD;
    m_playerAddr = 0;
    m_xyzAddr = 0;
    m_shieldAddr = 0;
    m_manaAddr = 0;
    m_spiritAddr = 0;
    m_nativeTerrain = {};
    m_terrainAddr = 0;
    m_baselineWidened = false;
    if (m_entities) m_entities->ClearEntities();
    if (m_townQuestEngine) {
        m_townQuestEngine->Reset();
    }
    const std::string msg = "[AutoScan] Chuyen canh sang map moi: '" + areaName + "'. Cho tai man hinh xong de chup baseline...";
    std::cout << msg << std::endl;
    CoreLog(msg);
}
bool GameSession::IsValidLifeComponent(uintptr_t addr, bool requireFullHP) const {
    if (addr == 0 || !m_reader) return false;
    uint32_t hp = 0, maxHP = 0;
    if (!m_reader->ReadValue<uint32_t>(addr, hp) ||
        !m_reader->ReadValue<uint32_t>(addr + 4, maxHP)) {
        return false;
    }
    const bool isCI = (maxHP == 1);
    // Dải máu hợp lý của PoE2 (loại trừ giá trị 255 / 0xFF memset, texture/glyph 4094)
    if (!isCI) {
        if (maxHP < 50 || maxHP > 25000 || maxHP == 255 || maxHP == 4094 || maxHP == 0x0FFE) return false;
        if (hp == 0 || hp > maxHP) return false;
    } else {
        if (hp > 1) return false;
        if (requireFullHP && hp != 1) return false;
    }

    uint32_t val8 = 0, val12 = 0, val16 = 0;
    m_reader->ReadValue<uint32_t>(addr + 8, val8);
    m_reader->ReadValue<uint32_t>(addr + 12, val12);
    m_reader->ReadValue<uint32_t>(addr + 16, val16);

    uint32_t val24 = 0, val28 = 0;
    m_reader->ReadValue<uint32_t>(addr + 24, val24);
    m_reader->ReadValue<uint32_t>(addr + 28, val28);

    bool hasValidCI = false;
    if (isCI) {
        // BẤT BIẾN CI: Thắt chặt kiểm tra: val16 (max ES) phải >= 50 và <= 50000,
        // val12 (cur ES) phải <= val16 và val12 > 0. Bất kỳ block nào có val16 < 50
        // không được coi là LifeComponent hợp lệ của nhân vật CI!
        if (val16 < 50 || val16 > 50000 || val12 > val16 || val12 == 0) {
            return false;
        }
        hasValidCI = true;
    }

    if (!isCI) {
        // Kháng Uniform Memset Buffer (trường +8, +12, +16 không bao giờ bằng maxHP trong LifeComponent)
        if (val12 == maxHP) return false;
        if (val8 == maxHP && val12 == maxHP) return false;
        if (val16 == maxHP) return false;

        // Kháng UI Glyph Buffer / Sequential Index Buffer (chuỗi tăng dần liên tiếp n, n, n+1, n+1...)
        if (val8 == maxHP + 1 || val8 == hp + 1) return false;
        if (val12 == maxHP + 1 || val12 == maxHP + 2) return false;
        if (val24 == maxHP + 1 || val24 == hp + 1) return false;
        if (val28 == maxHP + 1 || val28 == hp + 1) return false;
        if (val24 != 0 && val24 == val28 && (val24 == maxHP + 1 || val24 == hp + 1)) return false;
    }

    // ================================================================
    // BẤT BIẾN ES POE2 (RCA 2026-09-07 19:26 — thay magic "778/778"):
    //   Layout chuẩn: +12 = ES cur, +16 = ES max -> ES cur không thể
    //   vượt 20000. Block giả thật có +12 = 32763 (0x7FFB). Nếu +12
    //   vượt cap mà KHÔNG có bằng chứng Mana pool (+24/+28) hoặc
    //   VitalStruct pointer (+88) hoặc CI hợp lệ -> block giả, loại bỏ.
    // ================================================================
    const bool esCurPlausible = (val12 <= 20000 || hasValidCI);
    if (!esCurPlausible) {
        bool hasStructuralProof = false;
        // Bằng chứng 1: Mana pool {cur, max} hợp lệ tại +24/+28
        if (val28 >= 15 && val28 <= 50000 && val24 <= val28 && val28 != maxHP) {
            hasStructuralProof = true;
        }
        // Bằng chứng 2: VitalStruct pointer hợp lệ tại +88
        // RCA 21:03: pointer thật trỏ tới vùng đọc được với ràng buộc lỏng vẫn giúp
        // block giả qua gate -> siết: pMax <= 20000 và pCur <= pMax (bất biến pool).
        if (!hasStructuralProof) {
            uint64_t vPtr = 0;
            if (m_reader->ReadValue<uint64_t>(addr + 88, vPtr) &&
                vPtr >= 0x10000 && vPtr <= 0x7FFFFFFEFFFFULL) {
                uint32_t pCur = 0, pMax = 0;
                if (m_reader->ReadValue<uint32_t>(static_cast<uintptr_t>(vPtr), pCur) &&
                    m_reader->ReadValue<uint32_t>(static_cast<uintptr_t>(vPtr + 4), pMax) &&
                    pMax >= 15 && pMax <= 20000 && pCur <= pMax && pMax != maxHP) {
                    hasStructuralProof = true;
                }
            }
        }
        if (!hasStructuralProof) return false;
    }

    if (m_expectedMaxHP > 0) {
        // Cho phép m_playerAddr đã khóa được kiểm tra cấu trúc khi đổi maxHP (level up / thay đồ)
        if (addr == m_playerAddr && (maxHP == 1 || (maxHP >= 200 && maxHP <= 20000)) && maxHP != m_expectedMaxHP) {
            // Hợp lệ cho trường hợp Dynamic Level-Up / Gear Change / Chuyển CI
        } else {
            if (maxHP != m_expectedMaxHP) return false;
            if (requireFullHP && hp != m_expectedMaxHP) return false;
        }
    } else {
        // Cross-Vital Integrity Check (Chế độ tự động / zero user input):
        // Nhân vật Player POE2 bắt buộc phải đi kèm cấu trúc hợp lệ (Chữ ký ECS {2, 4},
        // Companion Mana pool, hoặc Deep Component Pointers).
        // Loại trừ triệt để quái, NPC, glyph buffer hoặc vùng nhớ rác.

        // 1. Chữ ký ECS POE2 LifeComponent: [-8] == 2 && [-4] == 4
        uint32_t prev8 = 0, prev4 = 0;
        bool hasLifeSig = false;
        if (addr >= 8) {
            if (m_reader->ReadValue<uint32_t>(addr - 8, prev8) &&
                m_reader->ReadValue<uint32_t>(addr - 4, prev4)) {
                if (prev8 == 2 && prev4 == 4) hasLifeSig = true;
            }
        }

        // 2. Flat Mana pool tại +0x18 (+24 bytes) và +0x1C (+28 bytes)
        bool hasFlatMana = false;
        if (val28 >= 15 && val28 <= 50000 && val24 <= val28 * 2 &&
            val28 != maxHP && val28 != maxHP + 1 && val28 != hp + 1 && val24 != maxHP + 1) {
            hasFlatMana = true;
        }

        // 3. Compact / Simulated Mana pool tại +0x08 (+8 bytes) và +0x0C (+12 bytes)
        bool hasSimMana = false;
        if (m_simulated && val12 >= 15 && val12 <= 50000 && val8 <= val12 * 2 &&
            val12 != maxHP && val12 != maxHP + 1 && val12 != hp + 1) {
            hasSimMana = true;
        }

        // 4. VitalStruct pointer tại +0x58 (+88 bytes)
        bool hasPtrMana = false;
        uint64_t vPtr = 0;
        if (m_reader->ReadValue<uint64_t>(addr + 88, vPtr) && vPtr >= 0x10000 && vPtr <= 0x7FFFFFFEFFFFULL) {
            uint32_t pCur = 0, pMax = 0;
            if (m_reader->ReadValue<uint32_t>(static_cast<uintptr_t>(vPtr), pCur) &&
                m_reader->ReadValue<uint32_t>(static_cast<uintptr_t>(vPtr + 4), pMax)) {
                if (pMax >= 15 && pMax <= 50000 && pCur <= pMax * 2 && pMax != maxHP && pMax != maxHP + 1) {
                    hasPtrMana = true;
                }
            }
        }

        // 5. Deep Component Pointers tại +0x20 hoặc +0x28
        bool hasCompPtr = false;
        uint64_t ptr20 = 0, ptr28 = 0;
        if (m_reader->ReadValue<uint64_t>(addr + 0x20, ptr20) && ptr20 >= 0x100000000ULL && ptr20 <= 0x7FFFFFFEFFFFULL && (ptr20 & 3) == 0) {
            uint32_t probe = 0;
            if (m_reader->ReadValue<uint32_t>(static_cast<uintptr_t>(ptr20), probe)) hasCompPtr = true;
        }
        if (!hasCompPtr && m_reader->ReadValue<uint64_t>(addr + 0x28, ptr28) && ptr28 >= 0x100000000ULL && ptr28 <= 0x7FFFFFFEFFFFULL && (ptr28 & 3) == 0) {
            uint32_t probe = 0;
            if (m_reader->ReadValue<uint32_t>(static_cast<uintptr_t>(ptr28), probe)) hasCompPtr = true;
        }

        if (m_simulated) {
            if (!hasLifeSig && !hasFlatMana && !hasSimMana && !hasPtrMana && !hasCompPtr && !hasValidCI) {
                return false;
            }
        } else {
            // Khóa chặn Bất Biến Client POE2 Thật (Rule 8 RCA):
            // Trên game thật, BẮT BUỘC phải có chữ ký ECS POE2 LifeComponent {2, 4} HOẶC
            // bằng chứng Mana pool thực tế (+24/+28 hoặc +88) HOẶC CI build có Energy Shield hợp lệ (+12/+16).
            // Tuyệt đối loại bỏ các block giả mạo chỉ có con trỏ heap ngẫu nhiên (hasCompPtr)
            // hoặc số liệu rác trùng khớp dải (như quái 500/500, UI widget).
            if (!hasLifeSig && !hasFlatMana && !hasPtrMana && !hasValidCI) {
                return false;
            }
        }
    }
    return true;
}

bool GameSession::IsValidManaAddress(uintptr_t addr) const {
    if (addr == 0 || !m_reader) return false;
    if (m_playerAddr != 0 && (addr == m_playerAddr || addr == m_playerAddr + 4 || addr == m_playerAddr + 12 || addr == m_playerAddr + 236)) {
        return false;
    }
    uint32_t val1 = 0, val2 = 0;
    if (!m_reader->ReadValue<uint32_t>(addr, val1) || !m_reader->ReadValue<uint32_t>(addr + 4, val2)) {
        return false;
    }
    uint32_t cur = val1;
    uint32_t mmax = val2;
    if (val1 > val2 && val1 >= 30 && val1 <= 50000 && val2 <= val1 * 2) {
        mmax = val1;
        cur = val2;
    }
    if (mmax < 15 || mmax > 50000 || cur > mmax * 2 || mmax == 255) return false;
    if ((cur == 0 && mmax == 1032) || (cur == 1032 && mmax == 0)) return false;
    if (m_expectedMaxMana > 0 && mmax != m_expectedMaxMana) return false;
    return true;
}

bool GameSession::IsValidShieldAddress(uintptr_t addr) const {
    if (addr == 0 || !m_reader) return false;
    if (addr < 0x10000 || addr > 0x7FFFFFFEFFFFULL) return false;
    if (m_playerAddr != 0 && addr == m_playerAddr) return false;
    uint32_t es = 0, esMax = 0;
    if (!m_reader->ReadValue<uint32_t>(addr, es) || !m_reader->ReadValue<uint32_t>(addr + 4, esMax)) {
        return false;
    }
    if (esMax == 0 || esMax > 20000 || es > esMax || esMax == 255) return false;
    return true;
}

bool GameSession::IsValidWardAddress(uintptr_t addr) const {
    if (addr == 0 || !m_reader) return false;
    if (addr < 0x10000 || addr > 0x7FFFFFFEFFFFULL) return false;
    if (m_playerAddr != 0 && (addr == m_playerAddr || addr == m_playerAddr + 4 || addr == m_manaAddr || addr == m_shieldAddr)) return false;
    uint32_t ward = 0, wardMax = 0;
    if (!m_reader->ReadValue<uint32_t>(addr, ward) || !m_reader->ReadValue<uint32_t>(addr + 4, wardMax)) {
        return false;
    }
    if (wardMax == 0 || wardMax > 20000 || ward > wardMax || wardMax == 255) return false;
    if (m_expectedMaxWard > 0 && wardMax != m_expectedMaxWard) return false;
    return true;
}

void GameSession::OnZoneLoadingFinished(const std::string& areaName)   {
        if (m_instanceTransitionPending) {
            m_playerAddr = 0;
            m_xyzAddr = 0;
            m_shieldAddr = 0;
            m_wardAddr = 0;
            m_manaAddr = 0;
            m_spiritAddr = 0;
            m_nativeTerrain = {};
            m_terrainAddr = 0;
            m_instanceTransitionPending = false;
        }
        m_currentSceneName = areaName;
        m_inGracePeriod = true;
        m_lastPosX = 0.0f;
        m_lastPosY = 0.0f;

        m_zoneLoadTimeMs = CurrentTimeMs();
        m_autoPosState = AutoPositionState::CAPTURING_STILL_BASELINE;
        if (m_entities) m_entities->ClearEntities();
        const std::string msg = "[AutoScan] Tai man hinh xong: '" + areaName + "'. Nhan vat dang dung yen tai cong vao (Grace Period).";
        std::cout << msg << std::endl;
        CoreLog(msg);
        overlay::SetLines(  {
                ">> AUTO-SCAN: TAI XONG MAP <<",        "Dung yen tai cong vao...",        "(Dang chuan bi chup baseline)"
        });

        // 1. Fast-Path Deterministic Pointer Chain Traversal O(1) (<0.001ms)
        bool resolved = false;
        if (!m_simulated && m_pointerResolver) {
            resolved = ResolveViaPointerChain();
            if (resolved) {
                const std::string succMsg = "[GameSession] 🎯 DETERMINISTIC POINTER CHAIN THÀNH CÔNG: Life=0x"
                                          + HexStr(m_playerAddr) + ", XYZ=0x" + HexStr(m_xyzAddr) + "!";
                std::cout << succMsg << std::endl;
                CoreLog(succMsg);
                SaveOffsets();
            }
        }

        if (!resolved) {
            AutoDetectPlayerHP(m_expectedMaxHP);
        }

        AutoDetectShield(m_expectedMaxES);
        AutoDetectWard(m_expectedMaxWard);
        AutoDetectManaPool(m_expectedMaxMana);
        AutoDetectSpirit(m_expectedMaxSpirit);
}
void GameSession::TriggerManualAutoScan()   {
        m_zoneLoadTimeMs = CurrentTimeMs();
        m_autoPosState = AutoPositionState::CAPTURING_STILL_BASELINE;
        const std::string msg = "[AutoScan] Kich hoat quet toa do thu cong (F11).";
        std::cout << msg << std::endl;
        CoreLog(msg);
        overlay::SetLines(  {
                ">> AUTO-SCAN: DANG LAY MAU <<",        "Dang lay mau toa do dung yen..."
    }
    );
}

void GameSession::OnPlayerRespawned() {
    m_playerAddr = 0;
    m_xyzAddr = 0;
    m_shieldAddr = 0;
    m_wardAddr = 0;
    m_manaAddr = 0;
    m_spiritAddr = 0;
    m_nativeTerrain = {};
    m_terrainAddr = 0;
    m_inGracePeriod = true;
    m_lastPosX = 0.0f;
    m_lastPosY = 0.0f;
    if (m_entities) m_entities->ClearEntities();
    m_autoPosState = AutoPositionState::CAPTURING_STILL_BASELINE;
    CoreLog("[GameSession] Nhan vat da hoi sinh tai Checkpoint -> Reset toan bo con tro entity va tu dong scan lai!");
    TriggerManualAutoScan();
}
void GameSession::StartStartupXYZCalibration() {
        if (!m_reader || m_simulated) return;
        if (m_autoPosState != AutoPositionState::IDLE) return;
        
        // Ưu tiên số 1: Thử giải mã con trỏ tất định O(1) qua PointerChainResolver
        if (ResolveViaPointerChain() && m_xyzAddr != 0) {
            CoreLog("[AutoScan] KHOI DONG XYZ: Giai ma thanh cong qua PointerChainResolver tai 0ms!");
            return;
        }

        m_zoneLoadTimeMs = CurrentTimeMs();
        m_autoPosState = AutoPositionState::CAPTURING_STILL_BASELINE;
        m_baselineWidened = false;
        m_firstStepStartMs = 0;
        const std::string msg = "[AutoScan] KHOI DONG calibration XYZ (fallback): chup baseline...";
        std::cout << msg << std::endl;
        CoreLog(msg);
        overlay::SetLines({
                ">> AUTO-SCAN: DANG LAY MAU <<",        "Dung yen - dang chup baseline toa do..."
        });
}
void GameSession::ProcessAutoPositionScan()   {
        if (m_autoPosState == AutoPositionState::IDLE || 
            m_autoPosState == AutoPositionState::WAITING_ZONE_LOAD ||
            m_autoPosState == AutoPositionState::RESOLVED)   {
                return;
    }
        const uint64_t now = CurrentTimeMs();
        if (m_autoPosState == AutoPositionState::CAPTURING_STILL_BASELINE)   {
/* Chờ 250ms sau khi màn hình tải biến mất để heap client game ổn định hoàn toàn */
        if (now - m_zoneLoadTimeMs < 250) return;
                if (!m_reader) return;
                if (!m_finder) m_finder = std::make_unique<PlayerFinder>(*m_reader);
                uintptr_t center = m_playerAddr;
                if (center == 0 && m_xyzAddr != 0) center = m_xyzAddr;
                constexpr size_t kNarrowWindow = 16 * 1024 * 1024;
/* 16MB quanh player */
        const size_t count = m_finder->SnapshotBaselineFloats(center, kNarrowWindow);
                const std::string msg = "[AutoScan] Da chup " + std::to_string(count)            + " float baseline tai cong vao. Dang cho buoc di chuyen dau tien...";
                std::cout << msg << std::endl;
                CoreLog(msg);
                overlay::SetLines(  {
                        ">> AUTO-SCAN: CHO BUOC CHAN <<",            "Hay buoc di 1-2 buoc...",            "(He thong se tu khoa XYZ)"
        }
        );
                m_autoPosState = AutoPositionState::WAITING_FIRST_STEP;
                m_lastScanCheckMs = now;
                m_firstStepStartMs = now;
                m_baselineWidened = false;
                return;
    }
        if (m_autoPosState == AutoPositionState::WAITING_FIRST_STEP)   {
/* Throttling: quét so sánh mỗi 100ms (250ms nếu baseline mở rộng) để nhẹ FPS */
        const uint64_t throttleMs = m_baselineWidened ? 250 : 100;
        if (now - m_lastScanCheckMs < throttleMs) return;
                m_lastScanCheckMs = now;
                if (!m_finder) return;
/* Tìm các float đã di chuyển so với baseline: delta >= 0.8f và <= 5000.0f */
        const size_t moved = m_finder->FindMovedFromBaseline(0.8f, 5000.0f);
        // Hỗ trợ nhận diện di chuyển ngay cả khi Z không đổi trên địa hình phẳng (như Sandswept Marsh) hoặc di chuyển đơn trục
        if (moved >= 1) {
            const size_t triples = m_finder->FindXYZTriples();
                        if (triples < 1 && !m_baselineWidened && now - m_firstStepStartMs >= 6000) {
                            /* RCA 2026-09-07: PositionedComponent có thể nằm ngoài cửa sổ
                               16MB quanh playerAddr (heap khác) -> mở rộng lên 64MB MỘT LẦN */
                            m_baselineWidened = true;
                            uintptr_t widenCenter = (m_playerAddr != 0) ? m_playerAddr : m_xyzAddr;
                            const size_t wideCount = m_finder->SnapshotBaselineFloats(widenCenter, 64u * 1024 * 1024);
                            const std::string wideMsg = "[AutoScan] Khong tim thay XYZ trong 16MB -> MO RONG baseline 64MB ("
                                + std::to_string(wideCount) + " float). Cho buoc di chuyen ke tiep...";
                            std::cout << wideMsg << std::endl;
                            CoreLog(wideMsg);
                        }
                        if (triples >= 1)   {
                                float x = 0.0f, y = 0.0f, z = 0.0f;
/* Tìm ứng viên đầu tiên thỏa mãn IsValidXYZ (đã được FindXYZTriples ưu tiên khoảng cách gần player) */
                for (uintptr_t candidateAddr : m_finder->XYZAddresses())   {
                                        if (m_finder->ReadPositionAt(candidateAddr, x, y, z) && PlayerFinder::IsValidXYZ(x, y, z))   {
                                                m_xyzAddr = candidateAddr;
                                                m_autoPosState = AutoPositionState::IDLE;
                                                const std::string succMsg = "[AutoScan] 🎯 KHOA TOA DO XYZ THANH CONG (Zero User Input)! 0x"                            + HexStr(m_xyzAddr) + " (" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ")";
                                                std::cout << succMsg << std::endl;
                                                CoreLog(succMsg);
                                                overlay::SetLines(  {
                                                        ">> XYZ AUTO-LOCKED! <<",                            "X=" + std::to_string(static_cast<int>(x)),                            "Y=" + std::to_string(static_cast<int>(y)),                            "Z=" + std::to_string(static_cast<int>(z))
                        }
                        );

                        // XYZ Interlock: Đối chiếu vị trí m_playerAddr với m_xyzAddr vừa tìm thấy!
                        // Nếu m_playerAddr chưa có hoặc nằm quá xa (> 16MB) hoặc không hợp lệ, lập tức tái hiệu chuẩn quanh m_xyzAddr!
                        // XYZ Interlock: Chỉ tái hiệu chuẩn HP quanh XYZ nếu m_playerAddr chưa có hoặc mất hiệu lực
                        if (m_playerAddr == 0 || !IsValidLifeComponent(m_playerAddr, false)) {
                            CoreLog("[AutoScan] >> m_playerAddr chưa có hoặc mất hiệu lực: Tự động tái hiệu chuẩn HP quanh XYZ (< 16MB)... <<");
                            AutoDetectPlayerHP(m_expectedMaxHP);
                        }

                        /* Tự động lưu vào offsets.toml */
                        SaveOffsets();
                                                break;
                    }
                }
            }
        }
    }
}
void GameSession::SaveOffsets()   {
        if (m_simulated) return;
/* Không ghi đè offsets khi đang chạy Unit Test mô phỏng */
    wchar_t exePathW[MAX_PATH] =   {
    }
    ;
        GetModuleFileNameW(nullptr, exePathW, MAX_PATH);
        char exeDir[MAX_PATH] =   {
    }
    ;
        WideCharToMultiByte(CP_UTF8, 0, exePathW, -1, exeDir, sizeof(exeDir), nullptr, nullptr);
        std::string dir = exeDir;
        auto slash = dir.find_last_of("\\/");
        std::string baseDir = (slash != std::string::npos) ? dir.substr(0, slash) : ".";
        std::string tomlPath = baseDir + "\\offsets.toml";
        OffsetsStore store;
        store.Load(tomlPath);

        // KIẾN TRÚC AN TOÀN TUYỆT ĐỐI: Không bao giờ lưu các địa chỉ heap tuyệt đối
        // (player_addr, xyz_addr, shield_addr, mana_addr, spirit_addr, ingame_addr, terrain_addr)
        // vào offsets.toml vì địa chỉ heap 64-bit thay đổi mỗi phiên chạy và mỗi lần đổi map (ASLR).
        store.Remove("player_addr");
        store.Remove("xyz_addr");
        store.Remove("shield_addr");
        store.Remove("mana_addr");
        store.Remove("spirit_addr");
        store.Remove("ingame_addr");
        store.Remove("terrain_addr");

        // File offsets.toml CHỈ ĐƯỢC LƯU các offset tương đối hoặc RVA tĩnh
        if (m_entityMapOffset != 0) {
            store.Set("entity_map_offset", m_entityMapOffset);
        }
        if (m_statOff != 0) {
            store.Set("stat_offset", m_statOff);
        }
        if (m_posOff != 0) {
            store.Set("pos_offset", m_posOff);
        }
        store.Save(tomlPath);
        std::cout << "[OffsetsStore] Đã cập nhật offsets.toml (chỉ lưu relative offsets: entity_offset=0x"
                  << std::hex << m_entityMapOffset << ", stat_offset=0x" << m_statOff
                  << ", pos_offset=0x" << m_posOff << std::dec << ")" << std::endl;
}
bool GameSession::AutoDetectManaPool(uint32_t expectedMaxMana) {
    if (m_playerAddr == 0 || !m_reader || !IsValidLifeComponent(m_playerAddr, false)) return false;

    /* Đọc trước HP/maxHP của người chơi để loại trừ triệt để ứng viên HP */
    uint32_t playerHP = 0, playerMaxHP = 0;
    m_reader->ReadValue<uint32_t>(m_playerAddr, playerHP);
    m_reader->ReadValue<uint32_t>(m_playerAddr + 4, playerMaxHP);

    constexpr int kMinOffset = -512;
    constexpr int kMaxOffset = 1024;
    constexpr size_t kBufSize = kMaxOffset - kMinOffset;
    std::vector<uint8_t> buf(kBufSize, 0);
    uintptr_t baseAddr = m_playerAddr + kMinOffset;
    if (!m_reader->Read(baseAddr, buf.data(), kBufSize)) {
        return false;
    }

    /* Ghi lại memory dump vùng LifeComponent vào log để chẩn đoán chính xác */
    std::string dumpLog = "[PlayerMemDump] Base=0x" + HexStr(m_playerAddr) + ":";
    for (int off = -64; off <= 160; off += 4) {
        size_t idx = off - kMinOffset;
        uint32_t val = 0;
        std::memcpy(&val, buf.data() + idx, 4);
        if (val != 0) {
            dumpLog += " [" + std::string(off >= 0 ? "+" : "") + std::to_string(off) + "]=" + std::to_string(val);
        }
    }
    CoreLog(dumpLog);

    uintptr_t bestAddr = 0;
    uint32_t bestCur = 0, bestMax = 0;
    int bestScore = -1;

    auto evaluateCandidate = [&](uintptr_t addr, int offset, uint32_t raw1, uint32_t raw2, const char* source) {
        // Loại trừ nếu trùng chính xác với giá trị rác 0/1032
        if (raw1 == 0 && raw2 == 1032) return;
        if (raw1 == 1032 && raw2 == 0) return;
        if (raw1 == 255 || raw2 == 255) return; // Kháng 0xFF memset buffer

        // Xác định cur và max (hỗ trợ cả {cur, max} và {max, cur})
        uint32_t cur = raw1;
        uint32_t max = raw2;
        if (raw1 > raw2 && raw1 >= 30 && raw1 <= 50000 && raw2 <= raw1 * 2) {
            max = raw1;
            cur = raw2;
        }

        // Loại trừ nếu trùng với HP người chơi
        if (playerMaxHP > 0 && max == playerMaxHP && (expectedMaxMana == 0 || expectedMaxMana != playerMaxHP)) return;
        if (playerHP > 0 && cur == playerHP && max == playerMaxHP) return;

        // Loại trừ Spirit (30/30)
        if (cur == 30 && max == 30) return;

        // Tiêu chuẩn cơ bản của pool
        if (max < 15 || max > 50000 || cur > max * 2) return;

        // BẤT BIẾN POE2 (Rule 8): Loại bỏ tuyệt đối sub-field của LifeComponent
        // Các trường con Life (base life, unreserved life) luôn có giá trị gần với playerMaxHP (khoảng cách <= 50)
        if (playerMaxHP > 0) {
            if (std::abs(static_cast<int>(max) - static_cast<int>(playerMaxHP)) <= 50) return;
            if (std::abs(static_cast<int>(cur) - static_cast<int>(playerMaxHP)) <= 50) return;
        }

        // BẤT BIẾN POE2: Không bao giờ nhầm Ward hoặc ES sang Mana
        if (m_wardAddr != 0 && (addr == m_wardAddr || addr == m_wardAddr + 4)) return;
        if (m_shieldAddr != 0 && (addr == m_shieldAddr || addr == m_shieldAddr + 4)) return;
        if (m_playerAddr != 0 && (addr == m_playerAddr + 32 || addr == m_playerAddr + 36)) return;
        if (m_expectedMaxWard > 0 && (max == m_expectedMaxWard || cur == m_expectedMaxWard) &&
            (expectedMaxMana == 0 || expectedMaxMana != m_expectedMaxWard)) return;
        if (m_expectedMaxES > 0 && (max == m_expectedMaxES || cur == m_expectedMaxES) &&
            (expectedMaxMana == 0 || expectedMaxMana != m_expectedMaxES)) return;

        int score = 0;
        if (cur == 0 && (expectedMaxMana == 0 || max != expectedMaxMana)) {
            // Phạt nặng mana = 0 khi nhân vật đang sống trong map
            score -= 200;
        } else if (cur > 0 && cur <= max) {
            score += 150; // Ưu tiên rất cao pool có cur > 0 và cur <= max
            if (cur == max) {
                score += 250; // Nhân vật đầy Mana khi ở Hideout / Grace Period
            } else if (cur * 4 < max) {
                score -= 200; // Phạt nặng Mana quá thấp khi ở trạng thái bình thường
            }
        }

        if (expectedMaxMana > 0 && max == expectedMaxMana) {
            score += 2000;
        } else if (expectedMaxMana > 0 && cur == expectedMaxMana) {
            score += 1500;
        } else if (max >= 40 && max <= 4000 && max != playerMaxHP) {
            score += 250;
        }

        // Ưu tiên cực cao cho các ứng viên nằm trong LifeComponent thật hoặc từ Component Pointer hợp lệ
        if (std::strncmp(source, "Check2", 6) == 0) {
            score += 450; // Ưu tiên ứng viên từ Component Pointer của LocalPlayer (ManaComponent)
        } else if (offset >= -64 && offset <= 256) {
            score += 400;
        }
        // Khớp chính xác với offset chuẩn của POE2 (+24) hoặc Test layout (+8)
        if (offset == 24 || offset == 8 || offset == 88) {
            score += 350;
        }

        if (max >= 30 && max <= 1500) {
            score += 50; // Dải Mana thực tế của POE2 Act 1 - End Game
        }

        if (score > bestScore) {
            bestScore = score;
            bestAddr = addr;
            bestCur = cur;
            bestMax = max;
            const std::string candLog = "[ManaDetect] " + std::string(source) + " at 0x" + HexStr(addr)
                + " (offset " + std::string(offset >= 0 ? "+" : "") + std::to_string(offset) + "): "
                + std::to_string(cur) + "/" + std::to_string(max) + " (score=" + std::to_string(score) + ")";
            std::cout << candLog << std::endl;
            CoreLog(candLog);
        }
    };

    /* Check 1: Quét vùng nhớ phẳng LifeComponent quanh m_playerAddr */
    for (int offset = kMinOffset; offset + 8 <= kMaxOffset; offset += 4) {
        // Tuyệt đối không kiểm tra chính các offset của HP (và ES/Ward trên client thật)
        if (offset == 0 || offset == 4) continue;
        if (!m_simulated && (offset == 8 || offset == 12 || offset == 16 || offset == 32 || offset == 36)) continue;

        size_t idx = offset - kMinOffset;
        uint32_t val1 = 0, val2 = 0;
        std::memcpy(&val1, buf.data() + idx, 4);
        std::memcpy(&val2, buf.data() + idx + 4, 4);

        evaluateCandidate(m_playerAddr + offset, offset, val1, val2, "Check1-Flat");

        // Kiểm tra format POE2 VitalStruct (+0x2C Max, +0x30 Current)
        if (offset + 0x34 <= kMaxOffset) {
            int actualOffset = offset + 0x2C;
            if (actualOffset != 0 && actualOffset != 4 && (m_simulated || (actualOffset != 8 && actualOffset != 12 && actualOffset != 16 && actualOffset != 32 && actualOffset != 36))) {
                uint32_t vMax = 0, vCur = 0;
                std::memcpy(&vMax, buf.data() + idx + 0x2C, 4);
                std::memcpy(&vCur, buf.data() + idx + 0x30, 4);
                evaluateCandidate(m_playerAddr + actualOffset, actualOffset, vMax, vCur, "Check1-VitalStruct");
            }
        }
    }

    /* Check 2: Dereference các component pointer trong LifeComponent/Player (quét động toàn bộ offset [-64, 384]) */
    // LUÔN LUÔN chạy Check 2 trừ khi Check 1 đã khớp chính xác expectedMaxMana (với expectedMaxMana > 0)
    const bool check1MatchedExpected = (expectedMaxMana > 0 && bestMax == expectedMaxMana);
    if (!m_simulated && !check1MatchedExpected) {
        std::unordered_set<uint64_t> visitedPtrs;
        for (int pOff = -64; pOff <= 384; pOff += 8) {
            uint64_t compPtr = 0;
            if (m_reader->ReadValue<uint64_t>(m_playerAddr + pOff, compPtr) &&
                compPtr >= 0x10000 && compPtr <= 0x7FFFFFFEFFFFULL &&
                (compPtr & 7) == 0 &&
                visitedPtrs.insert(compPtr).second) {
                std::vector<uint8_t> compBuf(2048, 0);
                if (m_reader->Read(static_cast<uintptr_t>(compPtr), compBuf.data(), compBuf.size())) {
                    for (size_t sub = 0; sub + 8 <= compBuf.size(); sub += 4) {
                        uint32_t c1 = 0, c2 = 0;
                        std::memcpy(&c1, compBuf.data() + sub, 4);
                        std::memcpy(&c2, compBuf.data() + sub + 4, 4);
                        evaluateCandidate(static_cast<uintptr_t>(compPtr + sub), static_cast<int>(sub), c1, c2, "Check2-DerefPtr");

                        // VitalStruct trong component
                        if (sub + 0x34 <= compBuf.size()) {
                            uint32_t vMax = 0, vCur = 0;
                            std::memcpy(&vMax, compBuf.data() + sub + 0x2C, 4);
                            std::memcpy(&vCur, compBuf.data() + sub + 0x30, 4);
                            evaluateCandidate(static_cast<uintptr_t>(compPtr + sub + 0x2C), static_cast<int>(sub + 0x2C), vMax, vCur, "Check2-DerefVital");
                        }
                    }
                }
            }
        }
    }

    /* Check 3: Tìm Mana theo danh sách candidates từ PlayerFinder nếu quét quanh m_playerAddr không thấy */
    if (bestScore < 200 || bestAddr == 0) {
        if (!m_finder && m_reader) m_finder = std::make_unique<PlayerFinder>(*m_reader);
        if (m_finder) {
            if (m_finder->ManaCandidates().empty() && m_playerAddr != 0) {
                m_finder->AutoScanHP(m_playerAddr, 16 * 1024 * 1024, 0);
            }
            for (const auto& cand : m_finder->ManaCandidates()) {
                if (cand.addr == m_playerAddr || cand.addr == m_playerAddr + 4 || cand.addr == m_shieldAddr) continue;
                uint32_t val1 = 0, val2 = 0;
                if (!m_reader->ReadValue<uint32_t>(cand.addr, val1) ||
                    !m_reader->ReadValue<uint32_t>(cand.addr + 4, val2)) {
                    val1 = cand.cur;
                    val2 = cand.max;
                }
                int offset = (m_playerAddr != 0) ? static_cast<int>(cand.addr - m_playerAddr) : 0;
                evaluateCandidate(cand.addr, offset, val1, val2, "Check3-FinderCandidate");
            }
        }
    }

    if (bestAddr != 0 && bestScore >= 200 && !(bestCur == 0 && bestMax == 1032)) {
        m_manaAddr = bestAddr;
        const std::string logMsg = "[GameSession] >> TỰ ĐỘNG KHÓA ĐỊA CHỈ MANA: 0x" + HexStr(m_manaAddr)
            + " (" + std::to_string(bestCur) + "/" + std::to_string(bestMax) + ") << - OK!";
        std::cout << logMsg << std::endl;
        CoreLog(logMsg);
        SaveOffsets();
        return true;
    }
    return false;
}

bool GameSession::AutoDetectShield(uint32_t expectedMaxShield) {
    if (m_playerAddr == 0 || !m_reader || !IsValidLifeComponent(m_playerAddr, false)) return false;

    if (expectedMaxShield > 0) {
        m_expectedMaxES = expectedMaxShield;
    }

    uint32_t playerHP = 0, playerMaxHP = 0;
    m_reader->ReadValue<uint32_t>(m_playerAddr, playerHP);
    m_reader->ReadValue<uint32_t>(m_playerAddr + 4, playerMaxHP);

    constexpr int kMinOffset = -512;
    constexpr int kMaxOffset = 1024;
    constexpr size_t kBufSize = kMaxOffset - kMinOffset;
    std::vector<uint8_t> buf(kBufSize, 0);
    uintptr_t baseAddr = m_playerAddr + kMinOffset;
    if (!m_reader->Read(baseAddr, buf.data(), kBufSize)) {
        return false;
    }

    uintptr_t bestAddr = 0;
    uint32_t bestCur = 0, bestMax = 0;
    int bestScore = -1;

    // Ưu tiên quét offset chuẩn POE2 (+12 Current ES, +16 Max ES) của m_playerAddr trước tiên
    uint32_t inlineESCur = 0, inlineESMax = 0;
    if (m_reader->ReadValue<uint32_t>(m_playerAddr + 12, inlineESCur) &&
        m_reader->ReadValue<uint32_t>(m_playerAddr + 16, inlineESMax)) {
        if (inlineESMax >= 20 && inlineESMax <= 50000 && inlineESCur <= inlineESMax && inlineESCur > 0) {
            if (expectedMaxShield == 0 || inlineESMax == expectedMaxShield || inlineESCur == expectedMaxShield) {
                bestAddr = m_playerAddr + 12;
                bestCur = inlineESCur;
                bestMax = inlineESMax;
                bestScore = (expectedMaxShield > 0 && (inlineESMax == expectedMaxShield || inlineESCur == expectedMaxShield)) ? 10000 : 3000;
            }
        }
    }

    for (int offset = kMinOffset; offset + 8 <= kMaxOffset; offset += 4) {
        uintptr_t candAddr = m_playerAddr + offset;
        if (candAddr == m_playerAddr || candAddr == m_playerAddr + 4 || candAddr == m_manaAddr) continue;
        // BẤT BIẾN POE2 (Rule 8): Offset +24 và +28 (+0x18, +0x1C) là Flat Mana pool của LifeComponent, không thể là Energy Shield
        if (offset == 24 || offset == 28) continue;

        size_t idx = offset - kMinOffset;
        uint32_t raw1 = 0, raw2 = 0;
        std::memcpy(&raw1, buf.data() + idx, 4);
        std::memcpy(&raw2, buf.data() + idx + 4, 4);

        if (raw1 == 0 && raw2 == 0) continue;
        if (raw1 == 255 || raw2 == 255) continue; // Kháng 0xFF memset

        uint32_t cur = raw1;
        uint32_t max = raw2;
        if (raw1 > raw2 && raw1 >= 20 && raw1 <= 50000 && raw2 <= raw1 * 2) {
            max = raw1;
            cur = raw2;
        }

        if (max < 20 || max > 50000 || cur > max) continue;
        // BẤT BIẾN POE2 (Rule 8): ES không thể có giá trị trùng hoặc gần maxHP (khoảng cách <= 50)
        if (playerMaxHP > 1 && (std::abs(static_cast<int>(max) - static_cast<int>(playerMaxHP)) <= 50 ||
                                std::abs(static_cast<int>(cur) - static_cast<int>(playerMaxHP)) <= 50)) continue;
        if (m_expectedMaxMana > 0 && max == m_expectedMaxMana) continue;

        int score = 0;
        if (expectedMaxShield > 0 && max == expectedMaxShield) {
            score += 5000;
        } else if (expectedMaxShield > 0 && cur == expectedMaxShield) {
            score += 4000;
        }
        if (cur > 0 && cur <= max && max >= 20 && max <= 50000) {
            score += 150;
            if (cur == max) {
                score += 100; // Đầy khiên tại Hideout / Town
            }
        }

        // Layout gần m_playerAddr
        if (offset >= -64 && offset <= 128) score += 100;

        if (score > bestScore) {
            bestScore = score;
            bestAddr = candAddr;
            bestCur = cur;
            bestMax = max;
        }

        // Kiểm tra format POE2 VitalStruct (+0x2C Max, +0x30 Current)
        if (offset + 0x34 <= kMaxOffset) {
            int actualOffset = offset + 0x2C;
            uintptr_t vAddr = m_playerAddr + actualOffset;
            if (vAddr != m_playerAddr && vAddr != m_playerAddr + 4 && vAddr != m_manaAddr && actualOffset != 24 && actualOffset != 28) {
                uint32_t vMax = 0, vCur = 0;
                std::memcpy(&vMax, buf.data() + idx + 0x2C, 4);
                std::memcpy(&vCur, buf.data() + idx + 0x30, 4);
                uint32_t c = vCur, m = vMax;
                if (vCur > vMax && vCur <= 50000 && vMax <= vCur * 2) {
                    m = vCur;
                    c = vMax;
                }
                if (m >= 20 && m <= 50000 && c <= m &&
                    !(playerMaxHP > 1 && (std::abs(static_cast<int>(m) - static_cast<int>(playerMaxHP)) <= 50 ||
                                          std::abs(static_cast<int>(c) - static_cast<int>(playerMaxHP)) <= 50))) {
                    int vScore = 0;
                    if (expectedMaxShield > 0 && m == expectedMaxShield) vScore += 5000;
                    else if (expectedMaxShield > 0 && c == expectedMaxShield) vScore += 4000;
                    if (c > 0 && c <= m && m >= 20 && m <= 50000) {
                        vScore += 150;
                        if (c == m) {
                            vScore += 100;
                        }
                    }
                    if (actualOffset >= -64 && actualOffset <= 128) vScore += 100;
                    if (vScore > bestScore) {
                        bestScore = vScore;
                        bestAddr = vAddr;
                        bestCur = c;
                        bestMax = m;
                    }
                }
            }
        }
    }

    /* Check 2: Dereference các component pointer trong LifeComponent/Player (quét động [-64, 384]) */
    const bool check1MatchedShield = (expectedMaxShield > 0 && bestMax == expectedMaxShield);
    if (!m_simulated && !check1MatchedShield) {
        std::unordered_set<uint64_t> visitedPtrs;
        for (int pOff = -64; pOff <= 384; pOff += 8) {
            uint64_t compPtr = 0;
            if (m_reader->ReadValue<uint64_t>(m_playerAddr + pOff, compPtr) &&
                compPtr >= 0x10000 && compPtr <= 0x7FFFFFFEFFFFULL &&
                (compPtr & 7) == 0 &&
                visitedPtrs.insert(compPtr).second) {
                std::vector<uint8_t> compBuf(2048, 0);
                if (m_reader->Read(static_cast<uintptr_t>(compPtr), compBuf.data(), compBuf.size())) {
                    for (size_t sub = 0; sub + 8 <= compBuf.size(); sub += 4) {
                        uintptr_t candAddr = static_cast<uintptr_t>(compPtr + sub);
                        if (candAddr == m_manaAddr) continue;
                        uint32_t c1 = 0, c2 = 0;
                        std::memcpy(&c1, compBuf.data() + sub, 4);
                        std::memcpy(&c2, compBuf.data() + sub + 4, 4);
                        if (c1 == 0 && c2 == 0) continue;
                        if (c1 == 255 || c2 == 255) continue;
                        uint32_t cur = c1, max = c2;
                        if (c1 > c2 && c1 >= 20 && c1 <= 50000 && c2 <= c1 * 2) {
                            max = c1;
                            cur = c2;
                        }
                        if (max < 20 || max > 20000 || cur > max) continue;
                        if (playerMaxHP > 1 && (std::abs(static_cast<int>(max) - static_cast<int>(playerMaxHP)) <= 50 ||
                                                std::abs(static_cast<int>(cur) - static_cast<int>(playerMaxHP)) <= 50)) continue;
                        if (m_expectedMaxMana > 0 && max == m_expectedMaxMana) continue;

                        int score = 0;
                        if (expectedMaxShield > 0 && max == expectedMaxShield) score += 5000;
                        else if (expectedMaxShield > 0 && cur == expectedMaxShield) score += 4000;
                        if (cur > 0 && cur <= max && max >= 20 && max <= 20000) {
                            score += 150;
                            if (cur == max) score += 100;
                        }

                        if (score > bestScore) {
                            bestScore = score;
                            bestAddr = candAddr;
                            bestCur = cur;
                            bestMax = max;
                        }

                        // VitalStruct trong component
                        if (sub + 0x34 <= compBuf.size()) {
                            uint32_t vMax = 0, vCur = 0;
                            std::memcpy(&vMax, compBuf.data() + sub + 0x2C, 4);
                            std::memcpy(&vCur, compBuf.data() + sub + 0x30, 4);
                            uint32_t vc = vCur, vm = vMax;
                            if (vCur > vMax && vCur <= 50000 && vMax <= vCur * 2) {
                                vm = vCur;
                                vc = vMax;
                            }
                            if (vm >= 20 && vm <= 50000 && vc <= vm &&
                                !(playerMaxHP > 1 && (std::abs(static_cast<int>(vm) - static_cast<int>(playerMaxHP)) <= 50 ||
                                                      std::abs(static_cast<int>(vc) - static_cast<int>(playerMaxHP)) <= 50))) {
                                int vScore = 0;
                                if (expectedMaxShield > 0 && vm == expectedMaxShield) vScore += 5000;
                                else if (expectedMaxShield > 0 && vc == expectedMaxShield) vScore += 4000;
                                if (vc > 0 && vc <= vm && vm >= 20 && vm <= 50000) {
                                    vScore += 150;
                                    if (vc == vm) vScore += 100;
                                }
                                if (vScore > bestScore) {
                                    bestScore = vScore;
                                    bestAddr = static_cast<uintptr_t>(compPtr + sub + 0x2C);
                                    bestCur = vc;
                                    bestMax = vm;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /* Check 3: Tìm Shield theo danh sách candidates từ PlayerFinder nếu quét quanh m_playerAddr không thấy */
    if (bestScore < 200 || bestAddr == 0) {
        if (!m_finder && m_reader) m_finder = std::make_unique<PlayerFinder>(*m_reader);
        if (m_finder) {
            if (m_finder->ShieldCandidates().empty() && m_playerAddr != 0) {
                m_finder->AutoScanHP(m_playerAddr, 16 * 1024 * 1024, 0);
            }
            for (const auto& cand : m_finder->ShieldCandidates()) {
            uintptr_t candAddr = cand.addr;
            if (candAddr == m_playerAddr || candAddr == m_playerAddr + 4 || candAddr == m_manaAddr ||
                candAddr == m_playerAddr + 24 || candAddr == m_playerAddr + 28) continue;

            uint32_t cur = 0, max = 0;
            if (!m_reader->ReadValue<uint32_t>(candAddr, cur) ||
                !m_reader->ReadValue<uint32_t>(candAddr + 4, max)) {
                cur = cand.cur;
                max = cand.max;
            }
            if (cur > max && cur >= 20 && cur <= 50000 && max <= cur * 2) {
                std::swap(cur, max);
            }
            if (max < 20 || max > 50000 || cur > max) continue;
            if (playerMaxHP > 1 && (std::abs(static_cast<int>(max) - static_cast<int>(playerMaxHP)) <= 50 ||
                                    std::abs(static_cast<int>(cur) - static_cast<int>(playerMaxHP)) <= 50)) continue;
            if (m_expectedMaxMana > 0 && max == m_expectedMaxMana) continue;

            int score = 0;
            if (expectedMaxShield > 0 && max == expectedMaxShield) score += 5000;
            else if (expectedMaxShield > 0 && cur == expectedMaxShield) score += 4000;
            if (cur > 0 && cur <= max && max >= 20 && max <= 50000) {
                score += 150;
                if (cur == max) score += 100;
            }
            if (m_playerAddr != 0) {
                uint64_t dist = (candAddr > m_playerAddr) ? (candAddr - m_playerAddr) : (m_playerAddr - candAddr);
                if (dist <= 64 * 1024) score += 100;
                else if (dist <= 1024 * 1024) score += 50;
            }

            if (score > bestScore) {
                bestScore = score;
                bestAddr = candAddr;
                bestCur = cur;
                bestMax = max;
            }
        }
    }
}

    if (bestAddr != 0 && (expectedMaxShield == 0 ? (bestScore >= 200) : (bestMax == expectedMaxShield || bestCur == expectedMaxShield))) {
        m_shieldAddr = bestAddr;
        const std::string msg = "[GameSession] 🛡️ TỰ ĐỘNG KHÓA ENERGY SHIELD: 0x" + HexStr(bestAddr)
                              + " (" + std::to_string(bestCur) + "/" + std::to_string(bestMax) + ") - OK!";
        std::cout << msg << std::endl;
        CoreLog(msg);
        SaveOffsets();
        return true;
    }
    return false;
}

bool GameSession::AutoDetectWard(uint32_t expectedMaxWard) {
    if (m_playerAddr == 0 || !m_reader || !IsValidLifeComponent(m_playerAddr, false)) return false;

    if (expectedMaxWard > 0) {
        m_expectedMaxWard = expectedMaxWard;
    }

    constexpr int kMinOffset = -512;
    constexpr int kMaxOffset = 1024;
    constexpr size_t kBufSize = kMaxOffset - kMinOffset;
    std::vector<uint8_t> buf(kBufSize, 0);
    uintptr_t baseAddr = m_playerAddr + kMinOffset;
    if (!m_reader->Read(baseAddr, buf.data(), kBufSize)) {
        return false;
    }

    uintptr_t bestAddr = 0;
    uint32_t bestCur = 0, bestMax = 0;
    int bestScore = -1;

    // Ưu tiên quét offset chuẩn POE2 (+32 Current Ward, +36 Max Ward) của m_playerAddr trước tiên
    if (m_manaAddr == m_playerAddr + 32) {
        m_manaAddr = 0;
        CoreLog("[GameSession] >> Reset m_manaAddr bị xung đột với offset Ward (+32) <<");
    }
    uint32_t inlineWardCur = 0, inlineWardMax = 0;
    if (m_reader->ReadValue<uint32_t>(m_playerAddr + 32, inlineWardCur) &&
        m_reader->ReadValue<uint32_t>(m_playerAddr + 36, inlineWardMax)) {
        if (inlineWardMax >= 20 && inlineWardMax <= 20000 && inlineWardCur <= inlineWardMax && inlineWardCur > 0) {
            if (m_expectedMaxWard == 0 || inlineWardMax == m_expectedMaxWard || inlineWardCur == m_expectedMaxWard) {
                bestAddr = m_playerAddr + 32;
                bestCur = inlineWardCur;
                bestMax = inlineWardMax;
                bestScore = (m_expectedMaxWard > 0 && (inlineWardMax == m_expectedMaxWard || inlineWardCur == m_expectedMaxWard)) ? 10000 : 3000;
            }
        }
    }

    for (int offset = kMinOffset; offset + 8 <= kMaxOffset; offset += 4) {
        uintptr_t candAddr = m_playerAddr + offset;
        if (candAddr == m_playerAddr || candAddr == m_playerAddr + 4 || candAddr == m_manaAddr || candAddr == m_shieldAddr) continue;
        if (offset == 24 || offset == 28) continue; // Flat Mana pool

        size_t idx = offset - kMinOffset;
        uint32_t raw1 = 0, raw2 = 0;
        std::memcpy(&raw1, buf.data() + idx, 4);
        std::memcpy(&raw2, buf.data() + idx + 4, 4);

        if (raw1 == 0 && raw2 == 0) continue;
        if (raw1 == 255 || raw2 == 255) continue;

        uint32_t cur = raw1;
        uint32_t max = raw2;
        if (raw1 > raw2 && raw1 >= 20 && raw1 <= 20000 && raw2 <= raw1 * 2) {
            max = raw1;
            cur = raw2;
        }

        if (max < 20 || max > 20000 || cur > max) continue;
        if (m_expectedMaxMana > 0 && max == m_expectedMaxMana) continue;
        if (m_expectedMaxES > 0 && max == m_expectedMaxES) continue;

        int score = 0;
        if (m_expectedMaxWard > 0 && max == m_expectedMaxWard) {
            score += 5000;
        } else if (m_expectedMaxWard > 0 && cur == m_expectedMaxWard) {
            score += 4000;
        }
        if (cur > 0 && cur <= max && max >= 20 && max <= 20000) {
            score += 150;
            if (cur == max) score += 100;
        }
        if (offset >= -64 && offset <= 128) score += 100;

        if (score > bestScore) {
            bestScore = score;
            bestAddr = candAddr;
            bestCur = cur;
            bestMax = max;
        }
    }

    if (bestAddr != 0 && (m_expectedMaxWard == 0 ? (bestScore >= 200) : (bestMax == m_expectedMaxWard || bestCur == m_expectedMaxWard))) {
        m_wardAddr = bestAddr;
        if (m_manaAddr == bestAddr) {
            m_manaAddr = 0;
            CoreLog("[GameSession] >> Giải phóng m_manaAddr bị xung đột với WardAddress <<");
        }
        const std::string msg = "[GameSession] 🎯 TỰ ĐỘNG KHÓA WARD: 0x" + HexStr(bestAddr)
                              + " (Ward=" + std::to_string(bestCur) + "/" + std::to_string(bestMax) + ") - OK!";
        std::cout << msg << std::endl;
        CoreLog(msg);
        return true;
    }
    return false;
}

bool GameSession::AutoDetectSpirit(uint32_t expectedMaxSpirit) {
    if (m_playerAddr == 0 || !m_reader || !IsValidLifeComponent(m_playerAddr, false)) return false;
    if (m_manaAddr == 0) return false; // Chạy sau khi Mana khóa xong để tránh nhầm pool

    uint32_t playerHP = 0, playerMaxHP = 0;
    m_reader->ReadValue<uint32_t>(m_playerAddr, playerHP);
    m_reader->ReadValue<uint32_t>(m_playerAddr + 4, playerMaxHP);

    constexpr int kMinOffset = -512;
    constexpr int kMaxOffset = 1024;
    constexpr size_t kBufSize = kMaxOffset - kMinOffset;
    std::vector<uint8_t> buf(kBufSize, 0);
    if (!m_reader->Read(m_playerAddr + kMinOffset, buf.data(), kBufSize)) {
        return false;
    }

    uintptr_t bestAddr = 0;
    uint32_t bestCur = 0, bestMax = 0;
    int bestScore = -1;

    for (int offset = kMinOffset; offset + 8 <= kMaxOffset; offset += 4) {
        const uintptr_t candAddr = m_playerAddr + offset;
        if (candAddr == m_playerAddr || candAddr == m_playerAddr + 4 ||
            candAddr == m_manaAddr || candAddr == m_shieldAddr) continue;

        const size_t idx = offset - kMinOffset;
        uint32_t raw1 = 0, raw2 = 0;
        std::memcpy(&raw1, buf.data() + idx, 4);
        std::memcpy(&raw2, buf.data() + idx + 4, 4);

        if (raw1 == 0 && raw2 == 0) continue;
        if (raw1 == 255 || raw2 == 255) continue;

        // Spirit lưu {cur, max}: cur <= max, max nhỏ (10 - 1000)
        const uint32_t cur = raw1;
        const uint32_t max = raw2;
        if (max < 10 || max > 1000 || cur > max) continue;

        // Loại họ Life (±16 quanh maxHP) và các pool đã biết
        if (playerMaxHP > 0 && max + 16 >= playerMaxHP && max <= playerMaxHP + 16) continue;
        if (m_expectedMaxMana > 0 && max + 16 >= m_expectedMaxMana && max <= m_expectedMaxMana + 16) continue;

        int score = 0;
        if (expectedMaxSpirit > 0 && max == expectedMaxSpirit) score += 1000;
        if (cur == max) score += 200;                     // Spirit đầy khi mới vào map
        if (offset >= -64 && offset <= 256) score += 100; // Cận LifeComponent

        if (score > bestScore) {
            bestScore = score;
            bestAddr = candAddr;
            bestCur = cur;
            bestMax = max;
        }
    }

    if (bestAddr != 0 && bestScore >= 300) {
        m_spiritAddr = bestAddr;
        const std::string msg = "[GameSession] 🔮 TỰ ĐỘNG KHÓA SPIRIT: 0x" + HexStr(bestAddr)
                              + " (" + std::to_string(bestCur) + "/" + std::to_string(bestMax) + ") - OK!";
        std::cout << msg << std::endl;
        CoreLog(msg);
        SaveOffsets();
        return true;
    }
    return false;
}

bool GameSession::AutoDetectPlayerHP(uint32_t expectedMaxHP) {
    if (!m_reader) return false;
    if (expectedMaxHP > 0) {
        m_expectedMaxHP = expectedMaxHP;
    }
    // Nếu m_playerAddr hiện tại đã là một LifeComponent hợp lệ và khớp với expectedMaxHP (hoặc expectedMaxHP == 0 kèm Mana >= 50) -> giữ nguyên
    if (m_playerAddr != 0 && IsValidLifeComponent(m_playerAddr, false)) {
        uint32_t curHP = 0, curMaxHP = 0;
        m_reader->ReadValue<uint32_t>(m_playerAddr, curHP);
        m_reader->ReadValue<uint32_t>(m_playerAddr + 4, curMaxHP);
        if (curMaxHP == 1) {
            m_expectedMaxHP = 1;
            return true;
        }
        if (m_expectedMaxHP > 0) {
            if (curMaxHP == m_expectedMaxHP) {
                return true;
            }
            // Dynamic Level-Up / Gear Change Adaptation:
            if (curMaxHP != m_expectedMaxHP && curMaxHP >= 200 && curMaxHP <= 20000) {
                CoreLog("[GameSession] >> Tu dong cap nhat MaxHP len cap / thay do: " + std::to_string(m_expectedMaxHP) + " -> " + std::to_string(curMaxHP));
                m_expectedMaxHP = curMaxHP;
                return true;
            }
        } else {
            // Chế độ Auto: chỉ giữ nếu đã xác nhận là Player có Mana >= 50
            uint32_t manaVal = 0;
            if (m_reader->ReadValue<uint32_t>(m_playerAddr + 28, manaVal) && manaVal >= 50 && manaVal <= 50000) {
                if (curMaxHP >= 200 && curMaxHP <= 20000) {
                    m_expectedMaxHP = curMaxHP;
                }
                return true;
            }
        }
    }
    if (m_simulated) return false;
    if (!m_finder) m_finder = std::make_unique<PlayerFinder>(*m_reader);

    if (m_expectedMaxHP == 1 || expectedMaxHP == 1 || m_expectedMaxES > 0) {
        m_finder->SetExpectedMaxES(m_expectedMaxES);
    }

    uintptr_t center = (m_xyzAddr != 0) ? m_xyzAddr : 0;
    uintptr_t found = m_finder->AutoScanHP(center, 16 * 1024 * 1024, m_expectedMaxHP);
    if (found == 0 && center != 0) {
        found = m_finder->AutoScanHP(0, 0, m_expectedMaxHP);
    }
    if (found != 0 && IsValidLifeComponent(found, false)) {
        uint32_t hp = 0, mhp = 0;
        m_reader->ReadValue<uint32_t>(found, hp);
        m_reader->ReadValue<uint32_t>(found + 4, mhp);
        m_playerAddr = found;
        const std::string msg = "[GameSession] 🎯 TỰ ĐỘNG KHÓA HP NHÂN VẬT (Zero User Input): 0x" + HexStr(found)
                              + " (HP=" + std::to_string(hp) + "/" + std::to_string(mhp) + ")!";
        std::cout << msg << std::endl;
        CoreLog(msg);
        SaveOffsets();

        // Tự động tìm Mana trước, sau đó tìm Shield (để Shield loại trừ m_manaAddr)
        if (m_manaAddr == 0) {
            AutoDetectManaPool(m_expectedMaxMana);
        }
        if (m_shieldAddr == 0) {
            AutoDetectShield(m_expectedMaxES);
        }
        if (m_wardAddr == 0) {
            AutoDetectWard(m_expectedMaxWard);
        }
        return true;
    }
    return false;
}

bool GameSession::AutoDetectTerrain(navigation::TerrainGrid* pGrid, float playerX, float playerY) {
    if (!m_reader) return false;

    // 1. Thử đọc từ m_terrainAddr nếu đã lưu từ phiên trước
    if (m_terrainAddr != 0) {
        if (memory::TerrainReader::ExtractFromTerrainStruct(*m_reader, m_terrainAddr, m_nativeTerrain)) {
            if (pGrid) pGrid->LoadFromNativeTerrain(m_nativeTerrain, playerX, playerY);
            return true;
        }
    }

    // 2. Thử quét tìm quanh m_inGameAddr nếu có
    if (m_inGameAddr != 0) {
        if (memory::TerrainReader::ScanTerrainCandidates(*m_reader, m_inGameAddr, 0x2000, m_nativeTerrain)) {
            if (pGrid) pGrid->LoadFromNativeTerrain(m_nativeTerrain, playerX, playerY);
            const std::string msg = "[TerrainReader] >> ĐÃ TỰ ĐỘNG KHÓA TERRAIN TỪ INGAMESTATE: "
                                  + std::to_string(m_nativeTerrain.cols) + "x" + std::to_string(m_nativeTerrain.rows)
                                  + " (" + std::to_string(static_cast<int>(m_nativeTerrain.WalkablePercentage())) + "% walkable) <<";
            std::cout << msg << std::endl;
            CoreLog(msg);
            return true;
        }
    }

    // 3. Thử quét quanh m_entitySlot / m_playerAddr
    uintptr_t searchBase = (m_entitySlot != 0) ? m_entitySlot : m_playerAddr;
    if (searchBase != 0) {
        uintptr_t scanStart = (searchBase > 0x10000) ? (searchBase - 0x10000) : 0x10000;
        if (memory::TerrainReader::ScanTerrainCandidates(*m_reader, scanStart, 0x20000, m_nativeTerrain)) {
            if (pGrid) pGrid->LoadFromNativeTerrain(m_nativeTerrain, playerX, playerY);
            const std::string msg = "[TerrainReader] >> ĐÃ TỰ ĐỘNG KHÓA TERRAIN TỪ VÙNG THỰC THỂ: "
                                  + std::to_string(m_nativeTerrain.cols) + "x" + std::to_string(m_nativeTerrain.rows)
                                  + " (" + std::to_string(static_cast<int>(m_nativeTerrain.WalkablePercentage())) + "% walkable) <<";
            std::cout << msg << std::endl;
            CoreLog(msg);
            return true;
        }
    }

    return false;
}
