#include "memory/player_finder.hpp"

#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstring>
#include <iostream>
#include <sstream>
#include <thread>
#include <unordered_set>
#include "common/logger.hpp"
#include "memory/game_layout.hpp"
#include "memory/vitals_fingerprint.hpp"

PlayerFinder::PlayerFinder(IMemoryReader& reader) : m_reader(reader) {}

size_t PlayerFinder::Scan(uint32_t maxHP) {
    m_maxHP = maxHP;
    m_candidates.clear();

    std::vector<uint8_t> buf(1024 * 1024);
    constexpr size_t kOverlap = 12;
    const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
    m_reader.ForEachReadableRegion([&](uintptr_t base, size_t size) {
        for (size_t off = 0; off + 12 <= size; off += step) {
            const size_t n = (std::min<size_t>)(buf.size(), size - off);
            if (!m_reader.Read(base + off, buf.data(), n)) continue;
            for (size_t i = 0; i + 12 <= n; i += 4) {
                uint32_t hp, m1, m2;
                std::memcpy(&hp, buf.data() + i, 4);
                std::memcpy(&m1, buf.data() + i + 4, 4);
                std::memcpy(&m2, buf.data() + i + 8, 4);
                if (m1 == maxHP && m2 == maxHP && hp <= maxHP) {
                    m_candidates.push_back(base + off + i);
                }
            }
        }
        return true; // tiếp tục region kế
    });
    return m_candidates.size();
}

size_t PlayerFinder::Rescan(uint32_t newHP) {
    std::vector<uintptr_t> kept;
    for (uintptr_t addr : m_candidates) {
        uint32_t v = 0;
        if (m_reader.ReadValue<uint32_t>(addr, v) && v == newHP) {
            kept.push_back(addr);
        }
    }
    m_candidates = std::move(kept);
    return m_candidates.size();
}

bool PlayerFinder::ReadStats(uint32_t& outHP, uint32_t& outMaxHP) const {
    for (uintptr_t addr : m_candidates) {
        uint32_t hp = 0, mhp = 0;
        if (m_reader.ReadValue<uint32_t>(addr, hp)
            && m_reader.ReadValue<uint32_t>(addr + 4, mhp)
            && mhp == m_maxHP) {
            outHP = hp;
            outMaxHP = mhp;
            return true;
        }
    }
    return false;
}

uintptr_t PlayerFinder::AutoScanHP(uintptr_t center, size_t window, uint32_t targetMaxHP) {
    const uintptr_t start = (center > window / 2) ? (center - window / 2) : 0;
    const uintptr_t end = (center != 0) ? (start + window) : 0xFFFFFFFFFFFFFFFFULL;

    m_shieldCandidates.clear();
    m_manaCandidates.clear();

    struct Candidate {
        uintptr_t addr = 0;
        uint32_t hp = 0;
        uint32_t maxHP = 0;
        uint32_t unreservedHP = 0;
        uintptr_t manaAddr = 0;
        uint32_t curMana = 0;
        uint32_t maxMana = 0;
        bool hasLifeSig = false;
        bool hasValidCI = false;
        bool hasValidPointers = false;
        bool isTypicalPlayerLife = false;
        bool isTypicalPlayerMana = false;
        int score = 0;
        uint64_t distFromCenter = UINT64_MAX;
    };

    std::vector<Candidate> hits;
    std::vector<VitalCandidate> rawShieldCandidates;
    std::vector<VitalCandidate> rawManaCandidates;
    std::vector<uint8_t> buf(1024 * 1024);
    const bool isSim = (m_reader.AttachedPid() == game_layout::kSimulatedPid || std::string(m_reader.BackendName()).find("Simulated") != std::string::npos);

    m_reader.ForEachHeapRegion([&](uintptr_t base, size_t size) -> bool {
        if (center != 0) {
            if (base + size <= start || base >= end) return true;
        }

        const uintptr_t rStart = (std::max)(base, start);
        const uintptr_t rEnd = (center != 0) ? (std::min)(base + size, end) : (base + size);
        if (rEnd <= rStart) return true;

        const size_t scanLen = rEnd - rStart;
        constexpr size_t kOverlap = 20; // HP..ES max = 20 byte để không cắt +12/+16 ở biên chunk
        const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
        for (size_t off = 0; off + 12 <= scanLen; off += step) {
            const size_t n = (std::min<size_t>)(buf.size(), scanLen - off);
            const uintptr_t curAddr = rStart + off;
            if (!m_reader.Read(curAddr, buf.data(), n)) continue;

            for (size_t i = 0; i + 12 <= n; i += 4) {
                uint32_t hp = 0, m1 = 0, m2 = 0;
                std::memcpy(&hp, buf.data() + i, 4);
                std::memcpy(&m1, buf.data() + i + 4, 4);
                std::memcpy(&m2, buf.data() + i + 8, 4);

                const uintptr_t candAddr = curAddr + i;

                // Thu thập các pool candidate hợp lệ trong RAM cho Shield (20 - 2000) và Mana (50 - 5000)
                uint32_t nextVal = 0;
                if (i + 16 <= n) {
                    std::memcpy(&nextVal, buf.data() + i + 12, 4);
                } else {
                    m_reader.ReadValue<uint32_t>(candAddr + 12, nextVal);
                }

                if (hp > 0 && hp <= m1 && m1 != 255 && candAddr >= 0x100000000ULL &&
                    m1 != nextVal && m2 != m1 + 1 && m2 != hp + 1 && m2 <= 5000000 && nextVal <= 5000000) {
                    if (m1 >= 20 && m1 <= 50000) {
                        rawShieldCandidates.push_back({ candAddr, hp, m1 });
                    }
                    if (m1 >= 50 && m1 <= 50000) {
                        rawManaCandidates.push_back({ candAddr, hp, m1 });
                    }
                }

                // 1. Dải máu hợp lý của nhân vật:
                // Bình thường: m1 >= 20 && m1 <= 25000 (loại trừ giá trị rác 255 / 0xFF memset buffer).
                // Chaos Inoculation (CI Keystone): m1 == 1, hp == 1.
                const bool isCI = (m1 == 1);
                if (!isCI) {
                    if (m1 < 20 || m1 > 25000 || m1 == 255) continue;
                }

                // 2. Kiểm tra trạng thái Grace Period / Town: hp == m1 (người chơi đầy máu tại cổng vào/thị trấn)
                if (targetMaxHP > 0) {
                    if (hp != m1 || m1 != targetMaxHP) continue;
                } else {
                    if (hp != m1) continue;
                }

                uint32_t val16 = 0;
                if (i + 20 <= n) {
                    std::memcpy(&val16, buf.data() + i + 16, 4);
                } else {
                    m_reader.ReadValue<uint32_t>(candAddr + 16, val16);
                }
                // Hiện trường 10/09/2026 21:12: ECS {2,4} + HP 826/826 nhưng +12/+16 = 388/54
                // (đảo) — Core khóa Ward-like thành Life, HUD ES 0/0. Không phải LifeComponent.
                if (vitals_fingerprint::IsInvertedEsPair(nextVal, val16)) continue;

                // 3. Kháng Uniform Memset Buffer (loại trừ bộ đệm đồ họa / color fill toàn số giống nhau)
                // Trong PoE2 LifeComponent, giá trị tại +12 không bao giờ bằng maxHP (thường là ES, cờ, hoặc 0)
                // Đối với CI (m1 == 1), +8 (unreservedHP) cũng là 1 nên chỉ áp dụng kiểm tra này khi m1 > 1.
                if (!isCI) {
                    if (m1 == nextVal || (m1 == m2 && m1 == nextVal)) continue;
                    if (m1 == val16) continue;
                    if (m2 > m1) continue; // unreservedHP > maxHP (dump 880 > 826)
                }

                // 4. Kiểm tra trường lân cận (+8 và +12 không phải giá trị rác/tràn số vô lý)
                if (m2 > 5000000 || nextVal > 5000000) continue;

                // 5. Kháng UI Glyph Buffer / Sequential Index Buffer:
                uint32_t val24 = 0, val28 = 0;
                if (i + 28 <= n) {
                    std::memcpy(&val24, buf.data() + i + 24, 4);
                } else {
                    m_reader.ReadValue<uint32_t>(candAddr + 24, val24);
                }
                if (i + 32 <= n) {
                    std::memcpy(&val28, buf.data() + i + 28, 4);
                } else {
                    m_reader.ReadValue<uint32_t>(candAddr + 28, val28);
                }

                // Loại trừ chuỗi quad index liên tiếp (0, 1, 2, 3... hoặc n, n, n+1, n+1...)
                if (!isCI) {
                    if (m2 == m1 + 1 || m2 == hp + 1) continue;
                    if (nextVal == m1 + 1 || nextVal == m1 + 2) continue;
                    if (val24 == hp + 1 || val24 == m1 + 1) continue;
                    if (val28 == hp + 1 || val28 == m1 + 1) continue;
                    if (val24 != 0 && val24 == val28 && (val24 == m1 + 1 || val24 == hp + 1)) continue;
                    if (m2 == hp + 1 && nextVal == hp + 2) continue;
                    if (m2 == hp + 2 && nextVal == hp + 3) continue;
                }

                // 6. Nhận diện đặc trưng ECS LifeComponent của POE 2: [-8] == 2 && [-4] == 4
                uint32_t prev8 = 0, prev4 = 0;
                bool hasPrev = false;
                if (i >= 8) {
                    std::memcpy(&prev8, buf.data() + i - 8, 4);
                    std::memcpy(&prev4, buf.data() + i - 4, 4);
                    hasPrev = true;
                } else if (candAddr >= 8) {
                    if (m_reader.ReadValue<uint32_t>(candAddr - 8, prev8) &&
                        m_reader.ReadValue<uint32_t>(candAddr - 4, prev4)) {
                        hasPrev = true;
                    }
                }
                const bool hasLifeSig = (hasPrev && prev8 == 2 && prev4 == 4);

                // Thắt chặt dải máu khi targetMaxHP == 0 (Chế độ tự động dò quét):
                // Nhân vật Level 30 thường có HP từ 300 đến 3500.
                // Loại trừ ngay các giá trị texture/glyph bất thường như 4094 (0x0FFE) hoặc > 5000 nếu không có signature {2, 4}
                if (targetMaxHP == 0) {
                    if (m1 == 4094 || m1 == 0x0FFE) continue; // Loại bỏ buffer texture/glyph
                    if (m1 > 5000 && !hasLifeSig) continue;   // Loại bỏ giá trị > 5000 không có signature ECS {2, 4}
                }

                // 7. Chấm điểm ứng viên (Scoring & Deep Ranking)
                int score = 0;

                // ĐẶC TRƯNG ECS LIFECOMPONENT CỦA POE 2: Cộng điểm thưởng cực đại (+300)
                if (hasLifeSig) {
                    score += 300;
                }

                bool hasValidCI = false;
                // Đánh giá dải máu hợp lý của Player POE2 (phân biệt với NPC / Elite Monster)
                if (m1 == 1) {
                    // Chaos Inoculation (CI Build): HP cố định = 1, sinh mệnh hoàn toàn dựa vào Energy Shield (ES)
                    // Đọc ES tại +12 (cur) và +16 (max)
                    uint32_t esCur = nextVal, esMax = 0;
                    if (i + 20 <= n) {
                        std::memcpy(&esMax, buf.data() + i + 16, 4);
                    } else {
                        m_reader.ReadValue<uint32_t>(candAddr + 16, esMax);
                    }

                    // BẤT BIẾN CI: Nhân vật CI bắt buộc phải có sinh mệnh từ Energy Shield.
                    // Nếu esMax < 50 (ví dụ entity rác 8/46), LOẠI BỎ NGAY LẬP TỨC (continue;) để không bao giờ chọn nhầm entity rác!
                    if (esMax < 50) {
                        continue;
                    }

                    if (hasLifeSig) {
                        score += 400; // Thưởng tối đa cho CI player có chữ ký ECS {2, 4}
                    }

                    if (esMax <= 50000 && esCur <= esMax * 2) {
                        hasValidCI = true;
                        score += 1500; // CI 1/1 + ES hợp lệ phải thắng ECS {2,4} + HP 600-1500 (826/826)
                        if (m_expectedMaxES > 0) {
                            if (esMax == m_expectedMaxES || std::abs(static_cast<int32_t>(esMax) - static_cast<int32_t>(m_expectedMaxES)) <= 10) {
                                score += 3000; // Thưởng cực lớn khi khớp m_expectedMaxES từ Optical Sensor
                            } else if (esMax >= 3000 && esMax <= 50000) {
                                score += 1000; // Thưởng thêm cho ES cấp cao T16
                            }
                        }
                    } else if (!hasLifeSig) {
                        score -= 2000; // Phạt cực nặng nếu không có chữ ký và ES không hợp lệ
                    }
                } else if (m1 < 200) {
                    score -= 350; // Phạt nặng Minion/Totem/Pet máu thấp (nhân vật POE2 Lv10+ không bao giờ max HP < 200)
                } else if (m1 >= 600 && m1 <= 1500) {
                    score += 250; // Điểm thưởng cho dải máu Player chuẩn Level 40-50 (700 - 1500 HP)
                } else if (m1 >= 300 && m1 < 600) {
                    score += 150; // Thang điểm tối đa: Dải máu chuẩn của Player Level 30
                } else if (m1 > 1500 && m1 <= 2500) {
                    score += 80;  // Dải máu Player build tanky / hybrid / high level
                } else if (m1 >= 200 && m1 < 300) {
                    score += 50;  // Dải máu Player giai đoạn đầu Act 1-2
                } else if (m1 > 2500 && m1 <= 4000) {
                    score += 30;  // Endgame Player
                } else if (m1 > 4000 && m1 <= 15000) {
                    score += 10;
                }

                // Loại trừ đặc trưng NPC / Elite Monster HP (ví dụ 1508 là HP cố định của NPC/quái tinh anh)
                if (m1 == 1508) {
                    score -= 150;
                }
                // Loại trừ đặc trưng World Map UI Marker (778 là thuộc tính render UI, không phải máu player)
                if (m1 == 778) {
                    score -= 500;
                }

                // Kiểm tra trường unreservedHP tại candAddr + 8:
                // Trong LifeComponent POE2, giá trị tại candAddr + 8 có thể là 1 (isAlive/ComponentFlag) hoặc unreservedHP.
                const uint32_t unreservedHP = m2;
                if (unreservedHP > 0 && unreservedHP <= m1) {
                    score += 100; // Thưởng điểm trường unreservedHP hợp lệ
                    if (unreservedHP == m1 || unreservedHP == 1) {
                        score += 30; // Player thông thường không reserve máu hoặc cờ isAlive == 1
                    }
                    // RCA 2026-09-07 23:47: Phân biệt Player vs NPC/Minion.
                    // Player tại town/grace period luôn có unreservedHP ≈ maxHP.
                    // NGHIÊM CẤM việc trừ 300 điểm khi unreservedHP == 1!
                    // Chỉ kiểm tra unreservedRatio < 0.1 KHI unreservedHP > 1 && unreservedHP < m1!
                    if (unreservedHP > 1 && unreservedHP < m1) {
                        double unreservedRatio = static_cast<double>(unreservedHP) / static_cast<double>(m1);
                        if (unreservedRatio < 0.1) {
                            score -= 300; // Cực kỳ đáng ngờ: nhiều khả năng là NPC/Minion/UI
                        } else if (unreservedRatio < 0.5) {
                            score -= 100; // Đáng ngờ: Player hiếm khi reserve > 50% HP
                        }
                    }
                } else if (unreservedHP == 0 && m1 > 0) {
                    // RCA 2026-09-08 00:08: Player LUÔN có unreservedHP > 0.
                    // Entity 579/579 có unreservedHP=0 là NPC/Summon, không phải player.
                    score -= 400; // Phạt cực nặng: unreservedHP=0 khi HP>0 là bất hợp lý cho player
                } else if (unreservedHP > m1 && unreservedHP < 5000000) {
                    score -= 50; // Bất hợp lý đối với LifeComponent
                }

                if (targetMaxHP == 0) {
                    if (m1 == 4094 || m1 == 0x0FFE) {
                        score -= 500;
                    }
                    if (m1 > 3500 && !hasLifeSig) {
                        score -= 200;
                    }
                }

                // Ưu tiên dải heap runtime x64 của PoE2 (thường >= 0x10000000000ULL, ví dụ 0x26... hoặc 0x3A...)
                if (candAddr >= 0x10000000000ULL) {
                    score += 100;
                } else {
                    score -= 100; // Phạt nặng vùng heap khởi tạo cấp thấp dễ chứa buffer render
                }

                // Chấm điểm cấu trúc ECS Component: kiểm tra tại +0x20 hoặc +0x28 có chứa con trỏ 64-bit hợp lệ
                // BẮT BUỘC: Con trỏ phải trỏ tới vùng nhớ đọc được thực tế (Deep Pointer Validation)
                uint64_t ptr20 = 0, ptr28 = 0;
                if (i + 0x28 <= n) {
                    std::memcpy(&ptr20, buf.data() + i + 0x20, 8);
                } else {
                    m_reader.ReadValue<uint64_t>(candAddr + 0x20, ptr20);
                }
                if (i + 0x30 <= n) {
                    std::memcpy(&ptr28, buf.data() + i + 0x28, 8);
                } else {
                    m_reader.ReadValue<uint64_t>(candAddr + 0x28, ptr28);
                }

                int validPtrCount = 0;
                if (ptr20 >= 0x100000000ULL && ptr20 <= 0x7FFFFFFEFFFFULL && (ptr20 & 3) == 0) {
                    uint32_t probe = 0;
                    if (m_reader.ReadValue<uint32_t>(static_cast<uintptr_t>(ptr20), probe)) {
                        score += 150;
                        ++validPtrCount;
                    }
                }
                if (ptr28 >= 0x100000000ULL && ptr28 <= 0x7FFFFFFEFFFFULL && (ptr28 & 3) == 0) {
                    uint32_t probe = 0;
                    if (m_reader.ReadValue<uint32_t>(static_cast<uintptr_t>(ptr28), probe)) {
                        score += 150;
                        ++validPtrCount;
                    }
                }

                // Cross-Vital Mana Detection: kiểm tra cả 3 đường bố cục Mana của PoE2
                uintptr_t fManaAddr = 0;
                uint32_t fManaCur = 0, fManaMax = 0;
                bool hasMana = false;

                // Đường 1: PoE2 flat Mana (+0x18 tức +24 bytes và +0x1C tức +28 bytes)
                if (val28 >= 15 && val28 <= 50000 && val24 <= val28 * 2 &&
                    val28 != m1 && val28 != m1 + 1 && val28 != m1 + 2 && val24 != m1 + 1) {
                    fManaAddr = candAddr + 24;
                    fManaCur = val24;
                    fManaMax = val28;
                    hasMana = true;
                }

                // Đường 2: Simulated / Compact Mana (+0x08 tức +8 bytes và +0x0C tức +12 bytes)
                // CHỈ áp dụng cho game ảo (isSim = true). Trên POE2 thật, +8 là unreservedHP, +12 là ES.
                if (!hasMana && isSim) {
                    if (nextVal >= 15 && nextVal <= 20000 && m2 <= nextVal &&
                        nextVal != m1 && nextVal != m1 + 1 && nextVal != m1 + 2 && m2 != m1 + 1) {
                        fManaAddr = candAddr + 8;
                        fManaCur = m2;
                        fManaMax = nextVal;
                        hasMana = true;
                    }
                }

                // Đường 3: VitalStruct pointer tại +0x58 (+88 bytes)
                // RCA 21:03: siết pMax <= 20000 && pCur <= pMax — chặn pointer trỏ tới
                // vùng readable bất kỳ của block giả làm "mana chứng thực".
                if (!hasMana) {
                    uint64_t vPtr = 0;
                    if (m_reader.ReadValue<uint64_t>(candAddr + 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 <= 20000 && pCur <= pMax && pMax != m1 && pMax != m1 + 1) {
                                fManaAddr = static_cast<uintptr_t>(vPtr);
                                fManaCur = pCur;
                                fManaMax = pMax;
                                hasMana = true;
                            }
                        }
                    }
                }

                // Cross-Vital Invariant Gate: Trong chế độ Auto (targetMaxHP == 0),
                // loại bỏ tuyệt đối các thực thể không có bất kỳ chứng thực cấu trúc nào (quái, NPC, glyph buffer).
                if (targetMaxHP == 0) {
                    if (!isSim) {
                        // Trên game POE2 thật: Bắt buộc phải có chữ ký ECS {2, 4} HOẶC bằng chứng Mana pool (+24/+28 hoặc +88) HOẶC CI có ES hợp lệ (+12/+16).
                        // Loại trừ triệt để quái, NPC, minion, glyph buffer hay struct 500/500 giả.
                        if (!hasLifeSig && !hasMana && !hasValidCI) {
                            continue;
                        }
                    } else {
                        if (!hasLifeSig && !hasMana && validPtrCount == 0 && !hasValidCI) {
                            continue;
                        }
                    }
                }

                // BẤT BIẾN ES (RCA 2026-09-07 19:26 — thay magic "778/778"):
                // +12 = ES cur phải <= 20000. Block giả thật có +12 = 32763 (0x7FFB)
                // mà không có Mana pool/pointer chứng thực nào (ngoại trừ CI hợp lệ).
                if (targetMaxHP == 0 && nextVal > 20000 && !hasMana && !hasValidCI) {
                    continue;
                }
                // RCA 2026-09-07 23:47: Trường ES tại offset +12 phải là giá trị hợp lý (0 - 5000).
                // Loại bỏ phạt điểm nextVal > 5000 khi isCI || hasValidCI (Bối cảnh 2026: CI build ES có thể tới 50,000).
                if (nextVal > 5000 && !isCI && !hasValidCI) {
                    score -= 200; // ES > 5000 là bất hợp lý cho entity thường không phải CI
                }

                // Minion Disambiguation Rule trong AutoScanHP:
                // Khi kiểm tra Mana:
                if (hasMana) {
                    if (fManaMax >= 50 && fManaMax <= 20000 && fManaCur <= fManaMax) {
                        score += 300; // Player Mana thật (+300)
                    } else if (fManaMax < 50) {
                        score -= 400; // Đặc trưng của Minion / Totem với Mana chỉ có 27 (-400)
                    } else {
                        score += 100;
                    }
                }

                // Khi ở chế độ Auto (targetMaxHP == 0):
                if (targetMaxHP == 0) {
                    if ((!hasMana || fManaMax < 50) && !hasValidCI) {
                        score -= 300; // Loại trừ các entity không có mana chuẩn player (-300) (bỏ qua nếu là CI build)
                    }
                    if (unreservedHP > 0 && (unreservedHP == m1 || unreservedHP == 1)) {
                        score += 150; // Player ở trạng thái bình thường/thị trấn có full máu (+150) hoặc cờ isAlive == 1
                    } else if (unreservedHP > 1 && unreservedHP < m1) {
                        score -= 100; // Minion thường có unreservedHP lẻ như 470/503 (-100)
                    }
                }

                uint64_t dist = UINT64_MAX;
                if (center != 0) {
                    dist = static_cast<uint64_t>(std::abs(static_cast<int64_t>(candAddr - center)));
                    if (dist < 64 * 1024) score += 300; // Ưu tiên rất cao < 64KB từ center XYZ
                }

                Candidate cand;
                cand.addr = candAddr;
                cand.hp = hp;
                cand.maxHP = m1;
                cand.unreservedHP = unreservedHP;
                cand.manaAddr = fManaAddr;
                cand.curMana = fManaCur;
                cand.maxMana = hasMana ? fManaMax : 0;
                cand.hasLifeSig = hasLifeSig;
                cand.hasValidCI = hasValidCI;
                cand.hasValidPointers = (validPtrCount > 0);
                cand.isTypicalPlayerLife = (m1 >= 200 && m1 <= 2000 && m1 != 1508 && m1 != 778);
                cand.isTypicalPlayerMana = (hasMana && fManaMax >= 50 && fManaMax <= 20000 && fManaCur <= fManaMax);
                cand.score = score;
                cand.distFromCenter = dist;
                hits.push_back(cand);

                // Early break nếu targetMaxHP > 0 và khớp hoàn hảo gần center
                if (targetMaxHP > 1 && score >= 300) {
                    m_candidates = { candAddr };
                    return false;
                }
                if (targetMaxHP == 1 && score >= 1500) {
                    m_candidates = { candAddr };
                    return false;
                }
            }
        }
        return true;
    });

    if (hits.empty()) return 0;

    // Sắp xếp và xếp hạng (Ranking)
    if (center != 0) {
        std::sort(hits.begin(), hits.end(), [targetMaxHP](const Candidate& a, const Candidate& b) {
            // Ưu tiên tuyệt đối ứng viên CI hợp lệ khi targetMaxHP == 1
            if (targetMaxHP == 1) {
                if (a.hasValidCI != b.hasValidCI) return a.hasValidCI > b.hasValidCI;
            } else if (a.hasValidCI != b.hasValidCI && (a.maxHP == 1 || b.maxHP == 1)) {
                return a.hasValidCI > b.hasValidCI;
            }
            // 1. Điểm tổng thể cấu trúc component (Player điểm cao vượt trội, Minion bị phạt điểm âm)
            if (a.score != b.score) return a.score > b.score;
            // 2. Ưu tiên Mana chuẩn player (isTypicalPlayerMana)
            if (a.isTypicalPlayerMana != b.isTypicalPlayerMana) return a.isTypicalPlayerMana > b.isTypicalPlayerMana;
            // 3. Ưu tiên full unreservedHP hoặc cờ isAlive == 1
            bool aFullUnres = (a.unreservedHP > 0 && (a.unreservedHP == a.maxHP || a.unreservedHP == 1));
            bool bFullUnres = (b.unreservedHP > 0 && (b.unreservedHP == b.maxHP || b.unreservedHP == 1));
            if (aFullUnres != bFullUnres) return aFullUnres > bFullUnres;
            // 4. Gần tọa độ người chơi nhất (< 64KB) chọn trước khi cùng tier điểm
            bool aNear = (a.distFromCenter < 64 * 1024);
            bool bNear = (b.distFromCenter < 64 * 1024);
            if (aNear != bNear) return aNear > bNear;
            if (a.isTypicalPlayerLife != b.isTypicalPlayerLife) return a.isTypicalPlayerLife > b.isTypicalPlayerLife;
            return a.distFromCenter < b.distFromCenter;
        });
    } else {
        std::sort(hits.begin(), hits.end(), [targetMaxHP](const Candidate& a, const Candidate& b) {
            // Ưu tiên tuyệt đối ứng viên CI hợp lệ khi targetMaxHP == 1
            if (targetMaxHP == 1) {
                if (a.hasValidCI != b.hasValidCI) return a.hasValidCI > b.hasValidCI;
            } else if (a.hasValidCI != b.hasValidCI && (a.maxHP == 1 || b.maxHP == 1)) {
                return a.hasValidCI > b.hasValidCI;
            }
            // 1. Điểm tổng thể cấu trúc component
            if (a.score != b.score) return a.score > b.score;

            // 2. Ưu tiên Mana chuẩn player (isTypicalPlayerMana)
            if (a.isTypicalPlayerMana != b.isTypicalPlayerMana) {
                return a.isTypicalPlayerMana > b.isTypicalPlayerMana;
            }

            // 3. Ưu tiên unreservedHP full hoặc cờ isAlive == 1 (Player thường không reserve máu)
            bool aFullUnres = (a.unreservedHP > 0 && (a.unreservedHP == a.maxHP || a.unreservedHP == 1));
            bool bFullUnres = (b.unreservedHP > 0 && (b.unreservedHP == b.maxHP || b.unreservedHP == 1));
            if (aFullUnres != bFullUnres) {
                return aFullUnres > bFullUnres;
            }

            // 4. Heuristic tie-break: Ưu tiên chỉ số dải máu Player điển hình (Level 30: 300 - 1200 HP)
            if (a.isTypicalPlayerLife != b.isTypicalPlayerLife) {
                return a.isTypicalPlayerLife > b.isTypicalPlayerLife;
            }

            // 5. Heuristic tie-break: Ưu tiên có ECS LifeComponent signature {2, 4}
            if (a.hasLifeSig != b.hasLifeSig) {
                return a.hasLifeSig > b.hasLifeSig;
            }

            // 6. Heuristic tie-break: Ưu tiên có Deep Pointer trỏ tới vùng đọc được
            if (a.hasValidPointers != b.hasValidPointers) {
                return a.hasValidPointers > b.hasValidPointers;
            }

            // 7. Heuristic tie-break: Địa chỉ vùng entity chính (player entity được cấp phát sớm hơn quái trong zone heap)
            return a.addr < b.addr;
        });
    }

    m_candidates = { hits.front().addr };

    const uintptr_t bestAddr = hits.front().addr;
    const uint32_t bestHP = hits.front().maxHP;

    // 1. Thu thập từ hits: "Khi tìm thấy candidate có m1 >= 20 && m1 <= 2000 && m1 != bestHP: Lưu lại làm candidate cho Energy Shield!"
    for (const auto& c : hits) {
        if (c.maxHP >= 20 && c.maxHP <= 2000 && c.maxHP != bestHP) {
            m_shieldCandidates.push_back({ c.addr, c.hp, c.maxHP });
        }
        if (c.maxHP >= 50 && c.maxHP <= 5000 && c.maxHP != bestHP) {
            m_manaCandidates.push_back({ c.addr, c.hp, c.maxHP });
        }
    }

    // 2. Thu thập từ các pool candidate hợp lệ trong RAM
    for (const auto& sc : rawShieldCandidates) {
        if (sc.max != bestHP && sc.addr != bestAddr && sc.addr != bestAddr + 4) {
            m_shieldCandidates.push_back(sc);
        }
    }
    for (const auto& mc : rawManaCandidates) {
        if (mc.max != bestHP && mc.addr != bestAddr && mc.addr != bestAddr + 4) {
            m_manaCandidates.push_back(mc);
        }
    }

    // 3. Nếu candidate tốt nhất của Player có Mana từ Cross-Vital Mana Detection, đưa lên đầu danh sách Mana
    if (hits.front().manaAddr != 0 && hits.front().maxMana >= 50 && hits.front().maxMana <= 5000) {
        m_manaCandidates.insert(m_manaCandidates.begin(), { hits.front().manaAddr, hits.front().curMana, hits.front().maxMana });
    }

    // 4. Deduplicate và sắp xếp theo khoảng cách tới bestAddr
    auto dedupeAndSort = [bestAddr](std::vector<VitalCandidate>& vec) {
        std::unordered_set<uintptr_t> seen;
        std::vector<VitalCandidate> unique;
        for (const auto& v : vec) {
            if (seen.insert(v.addr).second) {
                unique.push_back(v);
            }
        }
        std::sort(unique.begin(), unique.end(), [bestAddr](const VitalCandidate& a, const VitalCandidate& b) {
            uint64_t distA = (a.addr > bestAddr) ? (a.addr - bestAddr) : (bestAddr - a.addr);
            uint64_t distB = (b.addr > bestAddr) ? (b.addr - bestAddr) : (bestAddr - b.addr);
            return distA < distB;
        });
        if (unique.size() > 64) unique.resize(64);
        vec = std::move(unique);
    };

    dedupeAndSort(m_shieldCandidates);
    dedupeAndSort(m_manaCandidates);

    // Diagnostic: Log top candidates for debugging (CoreLog -> core_log.txt)
    {
        const size_t logCount = (std::min)(hits.size(), size_t(5));
        for (size_t i = 0; i < logCount; ++i) {
            const auto& c = hits[i];
            std::ostringstream oss;
            oss << "[AutoScanHP] Candidate #" << (i+1)
                << ": 0x" << std::hex << c.addr << std::dec
                << " HP=" << c.hp << "/" << c.maxHP
                << " unresHP=" << c.unreservedHP
                << " Mana=" << c.maxMana
                << " score=" << c.score
                << (c.hasLifeSig ? " [LifeSig]" : "")
                << (c.hasValidPointers ? " [Ptrs]" : "")
                << (c.isTypicalPlayerLife ? " [TypHP]" : "")
                << (c.isTypicalPlayerMana ? " [TypMana]" : "");
            CoreLog(oss.str());
        }
    }

    return hits.front().addr;
}

// ---- Float XYZ calibration ----
size_t PlayerFinder::ScanFloats(uintptr_t center, size_t window,
                                float fmin, float fmax) {
    m_floats.clear();
    m_xyz.clear();
    const uintptr_t start = center > window / 2 ? center - window / 2 : 0;
    const uintptr_t end = start + window;

    std::vector<uint8_t> buf(1024 * 1024);
    m_reader.ForEachReadableRegion([&](uintptr_t base, size_t size) -> bool {
        const uintptr_t rEnd = base + size;
        if (rEnd <= start || base >= end) return true; // ngoài cửa sổ

        const uintptr_t lo = (std::max)(base, start);
        const uintptr_t hi = (std::min)(rEnd, end);
        constexpr size_t kOverlap = 4;
        const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
        for (size_t off = lo - base; off + 4 <= hi - base; off += step) {
            const size_t n = (std::min<size_t>)(buf.size(), static_cast<size_t>(hi - base) - off);
            if (!m_reader.Read(base + off, buf.data(), n)) continue;
            for (size_t i = 0; i + 4 <= n; i += 4) {
                float f;
                std::memcpy(&f, buf.data() + i, 4);
                if (std::isfinite(f) && f >= fmin && f <= fmax) {
                    const uintptr_t fAddr = base + off + i;
                    if (m_floats.empty() || m_floats.back().first != fAddr) {
                        m_floats.push_back({fAddr, f});
                    }
                }
            }
        }
        return true;
    });

    // Nếu có center và tìm thấy đủ ứng viên -> hoàn tất
    if (m_floats.size() >= 100) {
        return m_floats.size();
    }

    // Fallback: Quét các readable heap regions lớn (tránh bị trượt khi playerAddr cũ bị lệch)
    m_reader.ForEachReadableRegion([&](uintptr_t base, size_t size) -> bool {
        // Chỉ quét các heap block người dùng (trên 64KB và dưới 512MB)
        if (size < 64 * 1024 || size > 512 * 1024 * 1024) return true;
        if (base < 0x10000000ULL) return true; // Bỏ qua vùng hệ thống thấp

        constexpr size_t kOverlap = 4;
        const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
        for (size_t off = 0; off + 4 <= size; off += step) {
            const size_t n = (std::min<size_t>)(buf.size(), size - off);
            if (!m_reader.Read(base + off, buf.data(), n)) continue;
            for (size_t i = 0; i + 4 <= n; i += 4) {
                float f;
                std::memcpy(&f, buf.data() + i, 4);
                if (std::isfinite(f) && f >= fmin && f <= fmax) {
                    const uintptr_t fAddr = base + off + i;
                    if (m_floats.empty() || m_floats.back().first != fAddr) {
                        m_floats.push_back({fAddr, f});
                    }
                }
            }
            if (m_floats.size() >= 100000) break; // Giới hạn bộ đệm tối đa 100k floats
        }
        return m_floats.size() < 100000;
    });

    return m_floats.size();
}

size_t PlayerFinder::FilterMoved(float minDelta) {
    std::vector<std::pair<uintptr_t, float>> kept;
    for (const auto& c : m_floats) {
        float now = 0;
        if (m_reader.ReadValue<float>(c.first, now)
            && std::fabs(now - c.second) >= minDelta) {
            kept.push_back({c.first, now});
        }
    }
    // Giữ nguyên danh sách nếu 0 match (cho user thử di chuyển lại)
    if (!kept.empty()) m_floats = std::move(kept);
    return kept.size();
}

size_t PlayerFinder::FilterStill(float maxDelta) {
    std::vector<std::pair<uintptr_t, float>> kept;
    for (const auto& c : m_floats) {
        float now = 0;
        if (m_reader.ReadValue<float>(c.first, now)
            && std::fabs(now - c.second) < maxDelta) {
            kept.push_back({c.first, now});
        }
    }
    // Giữ nguyên danh sách nếu 0 match (cho user đứng yên lại)
    if (!kept.empty()) m_floats = std::move(kept);
    return kept.size();
}

bool PlayerFinder::IsValidXYZ(float x, float y, float z) {
    // 1. Kiểm tra tính hữu hạn số học (loại bỏ NaN, Inf)
    if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z)) return false;

    // Loại trừ vector 0 (Chỉ loại trừ khi cả 3 trục đều bằng 0)
    if (std::fabs(x) < 0.001f && std::fabs(y) < 0.001f && std::fabs(z) < 0.001f) return false;

    // 2. Quy tắc Đa Dạng Không Gian 3D: Loại bỏ Uniform Scale Vector & Ma trận chiếu đồ họa
    // Tọa độ 3D thực của player hầu như không bao giờ có bất kỳ 2 trục nào bằng nhau (x == z hoặc x == y hoặc y == z)
    // Đặc biệt loại bỏ vector ma trận chiếu viewport đồ họa (ví dụ: 104.109375, 0.000000, 104.109375)
    if (std::fabs(x - z) < 0.01f || std::fabs(x - y) < 0.01f || std::fabs(y - z) < 0.01f) return false;

    // 3. Giới hạn bản đồ POE2: Bán kính map thực không vượt quá 60.000 đơn vị
    if (std::fabs(x) > 60000.0f || std::fabs(y) > 60000.0f) return false;

    // 4. Quy tắc Cao Độ (Z-Elevation): Cao độ mặt đất POE2 luôn nằm trong dải [-2000, 2000]
    if (std::fabs(z) > 2000.0f) return false;

    return true;
}

bool PlayerFinder::ReadPositionAt(uintptr_t addr, float& x, float& y, float& z) const {
    float v[3] = {0.0f, 0.0f, 0.0f};
    if (m_reader.Read(addr, v, sizeof(v))) {
        if (IsValidXYZ(v[0], v[1], v[2])) {
            x = v[0]; y = v[1]; z = v[2];
            return true;
        }
    }
    return false;
}

size_t PlayerFinder::FindXYZTriples() {
    if (m_floats.empty()) {
        m_xyz.clear();
        return 0;
    }

    // 1. Tập hợp các ô nhớ đã ghi nhận di chuyển
    std::unordered_set<uintptr_t> movedAddrs;
    movedAddrs.reserve(m_floats.size());
    for (const auto& f : m_floats) {
        movedAddrs.insert(f.first);
    }

    // 2. Thu thập candidate start addresses
    // Khi tìm thấy 1 float di chuyển tại địa chỉ A:
    // - Nếu A là trục X -> triple bắt đầu tại A
    // - Nếu A là trục Y -> triple bắt đầu tại A - 4
    // - Nếu A là trục Z -> triple bắt đầu tại A - 8
    std::vector<uintptr_t> candidateStarts;
    candidateStarts.reserve(m_floats.size() * 3);
    for (const auto& f : m_floats) {
        candidateStarts.push_back(f.first);
        if (f.first >= 4) candidateStarts.push_back(f.first - 4);
        if (f.first >= 8) candidateStarts.push_back(f.first - 8);
    }

    std::sort(candidateStarts.begin(), candidateStarts.end());
    candidateStarts.erase(std::unique(candidateStarts.begin(), candidateStarts.end()), candidateStarts.end());

    struct CandidateScore {
        uintptr_t addr = 0;
        int score = 0;
        float x = 0.0f;
        float y = 0.0f;
        float z = 0.0f;
    };

    std::vector<CandidateScore> scored;
    scored.reserve(candidateStarts.size());

    // 3. Đánh giá và chấm điểm từng candidate
    for (uintptr_t startAddr : candidateStarts) {
        float x = 0.0f, y = 0.0f, z = 0.0f;
        if (!ReadPositionAt(startAddr, x, y, z)) {
            continue;
        }

        const bool xMoved = movedAddrs.count(startAddr) > 0;
        const bool yMoved = movedAddrs.count(startAddr + 4) > 0;
        const bool zMoved = movedAddrs.count(startAddr + 8) > 0;

        // Bắt buộc ít nhất 1 trục phải thay đổi
        if (!xMoved && !yMoved && !zMoved) {
            continue;
        }

        int score = 0;
        if (xMoved) score += 100;
        if (yMoved) score += 100;
        if (zMoved) score += 50;

        // Thưởng điểm cao cho cặp trục mặt phẳng di chuyển 2D (X, Y) chuẩn POE2
        // Khi chạy trên mặt đất phẳng (như Sandswept Marsh), Z đứng yên (diff == 0.0f)
        if (xMoved && yMoved) score += 200;
        if (xMoved && zMoved) score += 120;
        if (yMoved && zMoved) score += 120;
        if (xMoved && yMoved && zMoved) score += 300;

        // Ưu tiên triple nằm gần PlayerState address (nếu đã tìm thấy HP)
        if (!m_candidates.empty()) {
            const uintptr_t playerAddr = m_candidates.front();
            const uint64_t dist = static_cast<uint64_t>(std::abs(static_cast<intptr_t>(startAddr - playerAddr)));
            if (dist < 64 * 1024) score += 300;
            else if (dist < 256 * 1024) score += 150;
            else if (dist < 2 * 1024 * 1024) score += 50;
        }

        scored.push_back({startAddr, score, x, y, z});
    }

    // 4. Sắp xếp theo điểm số giảm dần
    std::sort(scored.begin(), scored.end(), [](const CandidateScore& a, const CandidateScore& b) {
        if (a.score != b.score) return a.score > b.score;
        return a.addr < b.addr;
    });

    // 5. Chọn các triple không chồng lấn (cách nhau >= 12 bytes)
    m_xyz.clear();
    for (const auto& cand : scored) {
        bool overlaps = false;
        for (uintptr_t selected : m_xyz) {
            if (cand.addr + 12 > selected && cand.addr < selected + 12) {
                overlaps = true;
                break;
            }
        }
        if (!overlaps) {
            m_xyz.push_back(cand.addr);
        }
    }

    return m_xyz.size();
}

bool PlayerFinder::ReadPosition(float& x, float& y, float& z) const {
    for (uintptr_t a : m_xyz) {
        if (ReadPositionAt(a, x, y, z)) {
            return true;
        }
    }
    return false;
}

// ---- Tự động phát hiện di chuyển / đứng yên ----
size_t PlayerFinder::WaitForMoved(float minDelta, int timeoutSec, size_t minMatches) {
    const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeoutSec);
    std::vector<std::pair<uintptr_t, float>> prev = m_floats;
    size_t best = 0;
    std::vector<std::pair<uintptr_t, float>> bestKept;

    while (std::chrono::steady_clock::now() < deadline) {
        std::this_thread::sleep_for(std::chrono::milliseconds(400));
        size_t changed = 0;
        std::vector<std::pair<uintptr_t, float>> now;
        now.reserve(prev.size());
        for (const auto& c : prev) {
            float f = 0;
            if (m_reader.ReadValue<float>(c.first, f)) {
                now.push_back({c.first, f});
                if (std::fabs(f - c.second) >= minDelta) ++changed;
            }
        }
        if (changed > best) {
            best = changed;
            bestKept = now;
        }
        if (best >= minMatches) {
            m_floats = std::move(bestKept);
            return best;
        }
        prev = std::move(now);
    }
    if (best > 0) m_floats = std::move(bestKept);
    return best;
}

size_t PlayerFinder::WaitForStable(float maxDelta, int stableSamples, int timeoutSec) {
    const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeoutSec);
    std::vector<std::pair<uintptr_t, float>> prev = m_floats;
    int stable = 0;

    while (std::chrono::steady_clock::now() < deadline) {
        std::this_thread::sleep_for(std::chrono::milliseconds(400));
        size_t changed = 0;
        std::vector<std::pair<uintptr_t, float>> now;
        now.reserve(prev.size());
        for (const auto& c : prev) {
            float f = 0;
            if (m_reader.ReadValue<float>(c.first, f)) {
                now.push_back({c.first, f});
                if (std::fabs(f - c.second) >= maxDelta) ++changed;
            }
        }
        if (changed <= 5) {
            ++stable;
            if (stable >= stableSamples) {
                // Ổn định: áp still filter + cập nhật giá trị mới nhất
                std::vector<std::pair<uintptr_t, float>> kept;
                for (const auto& c : now) {
                    float old = 0;
                    for (const auto& o : prev) {
                        if (o.first == c.first) { old = o.second; break; }
                    }
                    if (std::fabs(c.second - old) < maxDelta) kept.push_back(c);
                }
                if (!kept.empty()) m_floats = std::move(kept);
                return m_floats.size();
            }
        } else {
            stable = 0;
        }
        prev = std::move(now);
    }
    // Hết giờ: áp still lần cuối với giá trị mới nhất
    size_t kept = 0;
    for (auto& c : m_floats) {
        float now = 0;
        if (m_reader.ReadValue<float>(c.first, now) && std::fabs(now - c.second) < maxDelta) {
            c.second = now;
            ++kept;
        }
    }
    return kept;
}

size_t PlayerFinder::SnapshotBaselineFloats(uintptr_t center, size_t window) {
    m_baselineFloats.clear();
    m_floats.clear();
    m_xyz.clear();

    constexpr size_t kMaxBaselineFloats = 16384; // Mở rộng baseline floats để bao quát toàn bộ cửa sổ 16MB
    const uintptr_t start = center > window / 2 ? center - window / 2 : 0;
    const uintptr_t end = start + window;

    std::vector<uint8_t> buf(1024 * 1024); // 1MB buffer

    if (center != 0) {
        struct RegionInfo {
            uintptr_t base;
            size_t size;
            uintptr_t lo;
            uintptr_t hi;
        };
        std::vector<RegionInfo> regions;

        m_reader.ForEachReadableRegion([&](uintptr_t base, size_t size) -> bool {
            const uintptr_t rEnd = base + size;
            if (rEnd <= start || base >= end) return true; // Ngoài cửa sổ hẹp quanh player

            const uintptr_t lo = (std::max)(base, start);
            const uintptr_t hi = (std::min)(rEnd, end);
            if (hi >= lo + 12) {
                regions.push_back({base, size, lo, hi});
            }
            return true;
        });

        // Ưu tiên quét các region gần vị trí center (player HP) nhất
        std::sort(regions.begin(), regions.end(), [center](const RegionInfo& a, const RegionInfo& b) {
            const uintptr_t midA = a.lo + (a.hi - a.lo) / 2;
            const uintptr_t midB = b.lo + (b.hi - b.lo) / 2;
            const uint64_t distA = (midA > center) ? (midA - center) : (center - midA);
            const uint64_t distB = (midB > center) ? (midB - center) : (center - midB);
            return distA < distB;
        });

        // Pass 1: Lọc các bộ ba float (x, y, z) thỏa mãn IsValidXYZ ngay tại baseline
        for (const auto& reg : regions) {
            constexpr size_t kOverlap = 12;
            const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
            for (size_t off = reg.lo - reg.base; off + 12 <= reg.hi - reg.base; off += step) {
                const size_t n = (std::min<size_t>)(buf.size(), static_cast<size_t>(reg.hi - reg.base) - off);
                if (!m_reader.Read(reg.base + off, buf.data(), n)) continue;
                for (size_t i = 0; i + 12 <= n; ) {
                    float x = 0.0f, y = 0.0f, z = 0.0f;
                    std::memcpy(&x, buf.data() + i, 4);
                    std::memcpy(&y, buf.data() + i + 4, 4);
                    std::memcpy(&z, buf.data() + i + 8, 4);
                    if (IsValidXYZ(x, y, z)) {
                        const uintptr_t fAddr = reg.base + off + i;
                        m_baselineFloats.push_back({fAddr, x});
                        m_baselineFloats.push_back({fAddr + 4, y});
                        m_baselineFloats.push_back({fAddr + 8, z});
                        if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
                        i += 12; // Bước nhảy 12 bytes tránh lưu trùng lặp cùng 1 triple
                    } else {
                        i += 4;
                    }
                }
                if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
            }
            if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
        }

        // Pass 2 (Fallback nếu phát hiện quá ít candidate triples): Thu thập float đơn lẻ hợp lệ quanh player
        if (m_baselineFloats.size() < 10) {
            for (const auto& reg : regions) {
                constexpr size_t kOverlap = 4;
                const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
                for (size_t off = reg.lo - reg.base; off + 4 <= reg.hi - reg.base; off += step) {
                    const size_t n = (std::min<size_t>)(buf.size(), static_cast<size_t>(reg.hi - reg.base) - off);
                    if (!m_reader.Read(reg.base + off, buf.data(), n)) continue;
                    for (size_t i = 0; i + 4 <= n; i += 4) {
                        float f = 0.0f;
                        std::memcpy(&f, buf.data() + i, 4);
                        if (std::isfinite(f) && std::fabs(f) >= 0.1f && std::fabs(f) <= 50000.0f) {
                            const uintptr_t fAddr = reg.base + off + i;
                            m_baselineFloats.push_back({fAddr, f});
                            if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
                        }
                    }
                    if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
                }
                if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
            }
        }
    } else {
        // Nếu center == 0 (HP chưa xác định / quét độc lập): Quét baseline triples thỏa IsValidXYZ trên toàn bộ readable heap
        m_reader.ForEachReadableRegion([&](uintptr_t base, size_t size) -> bool {
            if (size < 64 * 1024 || size > 64 * 1024 * 1024) return true;
            if (base < 0x10000000ULL) return true;

            constexpr size_t kOverlap = 12;
            const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
            for (size_t off = 0; off + 12 <= size; off += step) {
                const size_t n = (std::min<size_t>)(buf.size(), size - off);
                if (!m_reader.Read(base + off, buf.data(), n)) continue;
                for (size_t i = 0; i + 12 <= n; ) {
                    float x = 0.0f, y = 0.0f, z = 0.0f;
                    std::memcpy(&x, buf.data() + i, 4);
                    std::memcpy(&y, buf.data() + i + 4, 4);
                    std::memcpy(&z, buf.data() + i + 8, 4);
                    if (IsValidXYZ(x, y, z)) {
                        const uintptr_t fAddr = base + off + i;
                        m_baselineFloats.push_back({fAddr, x});
                        m_baselineFloats.push_back({fAddr + 4, y});
                        m_baselineFloats.push_back({fAddr + 8, z});
                        if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
                        i += 12;
                    } else {
                        i += 4;
                    }
                }
                if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
            }
            return m_baselineFloats.size() < kMaxBaselineFloats;
        });
    }

    // Nếu center chưa có hoặc quá ít float -> quét các heap user
    if (m_baselineFloats.size() < 10) {
        m_reader.ForEachReadableRegion([&](uintptr_t base, size_t size) -> bool {
            if (size < 64 * 1024 || size > 64 * 1024 * 1024) return true;
            if (base < 0x10000000ULL) return true;

            constexpr size_t kOverlap = 4;
            const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
            for (size_t off = 0; off + 4 <= size; off += step) {
                const size_t n = (std::min<size_t>)(buf.size(), size - off);
                if (!m_reader.Read(base + off, buf.data(), n)) continue;
                for (size_t i = 0; i + 4 <= n; i += 4) {
                    float f = 0.0f;
                    std::memcpy(&f, buf.data() + i, 4);
                    if (std::isfinite(f) && std::fabs(f) >= 0.1f && std::fabs(f) <= 50000.0f) {
                        const uintptr_t fAddr = base + off + i;
                        m_baselineFloats.push_back({fAddr, f});
                        if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
                    }
                }
                if (m_baselineFloats.size() >= kMaxBaselineFloats) break;
            }
            return m_baselineFloats.size() < kMaxBaselineFloats;
        });
    }

    // Sắp xếp theo thứ tự địa chỉ tăng dần và loại bỏ trùng lặp để tối ưu batch reading
    std::sort(m_baselineFloats.begin(), m_baselineFloats.end(),
        [](const auto& a, const auto& b) { return a.first < b.first; });
    m_baselineFloats.erase(
        std::unique(m_baselineFloats.begin(), m_baselineFloats.end(),
            [](const auto& a, const auto& b) { return a.first == b.first; }),
        m_baselineFloats.end());

    return m_baselineFloats.size();
}

size_t PlayerFinder::FindMovedFromBaseline(float minDelta, float maxDelta) {
    m_floats.clear();
    m_xyz.clear();

    if (m_baselineFloats.empty()) return 0;

    // Batch chunk reading: triệt tiêu hàng chục nghìn RPM syscalls mỗi 100ms thành vài chục syscalls
    constexpr size_t kMaxChunkGap = 128;        // Ghép các float cách nhau <= 128 bytes vào cùng 1 lần đọc
    constexpr size_t kMaxChunkSize = 64 * 1024; // Kích thước chunk tối đa 64KB

    size_t idx = 0;
    const size_t total = m_baselineFloats.size();
    std::vector<uint8_t> chunkBuf;

    while (idx < total) {
        const uintptr_t chunkStart = m_baselineFloats[idx].first;
        size_t endIdx = idx + 1;

        while (endIdx < total) {
            const uintptr_t nextAddr = m_baselineFloats[endIdx].first;
            if (nextAddr < chunkStart) break;
            if (nextAddr - m_baselineFloats[endIdx - 1].first > kMaxChunkGap) break;
            if (nextAddr + 4 - chunkStart > kMaxChunkSize) break;
            ++endIdx;
        }

        const size_t chunkLen = (m_baselineFloats[endIdx - 1].first + 4) - chunkStart;
        if (chunkBuf.size() < chunkLen) chunkBuf.resize(chunkLen);

        if (m_reader.Read(chunkStart, chunkBuf.data(), chunkLen)) {
            for (size_t i = idx; i < endIdx; ++i) {
                const uintptr_t addr = m_baselineFloats[i].first;
                const size_t off = addr - chunkStart;
                if (off + 4 <= chunkLen) {
                    float now = 0.0f;
                    std::memcpy(&now, chunkBuf.data() + off, 4);
                    const float diff = std::fabs(now - m_baselineFloats[i].second);
                    if (diff >= minDelta && diff <= maxDelta && std::isfinite(now)) {
                        if (std::fabs(now) >= 0.1f && std::fabs(now) <= 60000.0f) {
                            m_floats.push_back({addr, now});
                        }
                    }
                }
            }
        } else {
            // Fallback nếu trang nhớ không liền khối
            for (size_t i = idx; i < endIdx; ++i) {
                float now = 0.0f;
                if (m_reader.ReadValue<float>(m_baselineFloats[i].first, now)) {
                    const float diff = std::fabs(now - m_baselineFloats[i].second);
                    if (diff >= minDelta && diff <= maxDelta && std::isfinite(now)) {
                        if (std::fabs(now) >= 0.1f && std::fabs(now) <= 60000.0f) {
                            m_floats.push_back({m_baselineFloats[i].first, now});
                        }
                    }
                }
            }
        }

        idx = endIdx;
    }

    return m_floats.size();
}

// ---- Entity Array discovery (port từ entscan thực địa 2026-09-06) ----
bool PlayerFinder::FindEntityArray(uintptr_t statAddr, EntityArrayInfo& out) {
    out = {};

    // Stat block phải còn sống
    uint32_t curHP = 0;
    if (!m_reader.ReadValue<uint32_t>(statAddr, curHP)) return false;

    // Pass A: tìm mọi ô nhớ lưu con trỏ trỏ vào player entity object
    std::vector<std::pair<uintptr_t, uintptr_t>> ptrs; // (L, V)
    const uintptr_t wLo = statAddr > 0x40000 ? statAddr - 0x40000 : 0;
    const uintptr_t wHi = statAddr + 16;
    std::vector<uint8_t> buf(1024 * 1024);
    constexpr size_t kOverlap = 8;
    const size_t step = (buf.size() > kOverlap) ? (buf.size() - kOverlap) : buf.size();
    m_reader.ForEachReadableRegion([&](uintptr_t base, size_t size) {
        for (size_t off = 0; off + 8 <= size; off += step) {
            const size_t n = (std::min<size_t>)(buf.size(), size - off);
            if (!m_reader.Read(base + off, buf.data(), n)) continue;
            for (size_t i = 0; i + 8 <= n; i += 8) {
                uint64_t v;
                std::memcpy(&v, buf.data() + i, 8);
                if (v >= wLo && v <= wHi) {
                    ptrs.push_back({base + off + i, v});
                }
            }
        }
        return true;
    });
    if (ptrs.empty()) return false;

    // Ưu tiên V gần statAddr nhất (entity base)
    std::sort(ptrs.begin(), ptrs.end(), [](const auto& a, const auto& b) {
        return a.second > b.second;
    });

    // Pass B: với từng (L, V): statOff = statAddr - V; đi dọc mảng ±128 entry
    int tested = 0;
    uintptr_t lastV = 0;
    for (const auto& pv : ptrs) {
        if (pv.second == 0 || statAddr < pv.second) continue;
        const uintptr_t statOff = statAddr - pv.second;
        if (statOff > 0x40000) continue;
        if (pv.second == lastV) continue;
        lastV = pv.second;
        if (tested >= 16) break;
        ++tested;

        EntityArrayInfo hit;
        hit.slotAddr = pv.first;
        hit.statOff = statOff;

        for (int i = -128; i <= 128; ++i) {
            const uintptr_t slot = pv.first + static_cast<int64_t>(8) * i;
            uint64_t ent = 0;
            if (!m_reader.ReadValue<uint64_t>(slot, ent) || ent < 0x10000
                || ent > 0x7FFFFFFEFFFF) {
                continue;
            }
            ++hit.totalRead;
            uint32_t hp = 0;
            if (m_reader.ReadValue<uint32_t>(ent + statOff, hp) && hp > 0 && hp <= 500) {
                ++hit.plausible;
                if (static_cast<int>(hit.slots.size()) < 96) {
                    hit.slots.push_back({i, ent});
                    hit.hps.push_back({i, hp});
                }
            }
        }

        if (hit.plausible > out.plausible) out = std::move(hit);
        if (out.plausible >= 22) break; // đủ tốt
    }

    return out.plausible >= 8;
}