// ==========================================================
// Domain 2: Memory & Engine Unit Tests
// ==========================================================

#include "test_harness.hpp"

#include "common/protocol.hpp"
#include "ipc/shared_memory.hpp"
#include "input/human_curve.hpp"
#include "sensor/log_sensor.hpp"
#include "memory/imemory_reader.hpp"
#include "memory/rpm_reader.hpp"
#include "memory/aob_scanner.hpp"
#include "memory/offsets_store.hpp"
#include "memory/pe_fingerprint.hpp"
#include "memory/offset_registry.hpp"
#include "memory/simulated_reader.hpp"
#include "memory/entity_manager.hpp"
#include "memory/game_layout.hpp"
#include "memory/player_finder.hpp"
#include "memory/pointer_chain_resolver.hpp"
#include "memory/registry_live_label.hpp"
#include "memory/vitals_fingerprint.hpp"
#include "combat/reflex_manager.hpp"
#include "looting/loot_controller.hpp"
#include "combat/combo_manager.hpp"
#include "combat/kiting_engine.hpp"
#include "navigation/quest_navigator.hpp"
#include "navigation/terrain_grid.hpp"
#include "navigation/pathfinder.hpp"
#include "memory/terrain_reader.hpp"
#include "game_session.hpp"
#include "autologin/auto_login.hpp"
#include "navigation/town_quest_engine.hpp"
#include "common/coordinate_transform.hpp"
#include "brain/bot_brain.hpp"
#include "brain/area_events.hpp"
#include "brain/inventory_handler.hpp"
#include "combat/skills_table.hpp"
#include "combat/skill_definition.hpp"
#include "combat/skill_engine.hpp"
#include "brain/unstuck_handler.hpp"
#include "brain/map_device_handler.hpp"
#include "brain/character_fsm.hpp"
#include "brain/npc_interaction_handler.hpp"
#include "common/window_utils.hpp"
#include "common/math2d.hpp"
#include "common/string_utils.hpp"
#include "common/time_utils.hpp"
#include "common/toml_parser.hpp"
#include "common/logger.hpp"

#include <filesystem>
#include <atomic>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
#include <thread>
#include <vector>

// ==========================================================
// 8. AOB Scanner (pattern matching nhị phân)
// ==========================================================
void TestAobScanner() {
    std::cout << "[Test 8] AOB Scanner..." << std::endl;

    auto pat = AobPattern::Parse("48 8B 0D ? ? ? ?");
    CHECK(pat.Valid() && pat.Size() == 7, "Parse pattern thất bại");

    uint8_t buf[] = {0x90, 0x48, 0x8B, 0x0D, 0x11, 0x22, 0x33, 0x44, 0xCC};
    const intptr_t idx = AobScanner::Find(buf, sizeof(buf), pat);
    CHECK(idx == 1, "AOB không tìm thấy đúng vị trí");

    // Buffer không chứa pattern
    uint8_t empty[] = {0x90, 0x90, 0x48, 0x8B, 0xC1};
    CHECK(AobScanner::Find(empty, sizeof(empty), pat) == -1, "AOB phải báo không thấy");

    // Wildcard ở đầu pattern
    auto patWild = AobPattern::Parse("? 8B C1");
    uint8_t buf2[] = {0x48, 0x8B, 0xC1};
    CHECK(AobScanner::Find(buf2, sizeof(buf2), patWild) == 0, "Wildcard đầu không hoạt động");

    // Pattern sai cú pháp phải bị từ chối
    CHECK(!AobPattern::Parse("48 XZ 0D").Valid(), "Pattern sai cú pháp không bị từ chối");

    SimulatedMemoryReader simScan;
    CHECK(simScan.Initialize(), "sim AOB Initialize");
    CHECK(simScan.Attach(game_layout::kSimulatedPid), "sim AOB Attach");
    const uint8_t planted[] = {0x48, 0x8B, 0x0D, 0x11, 0x22, 0x33, 0x44, 0x90};
    CHECK(simScan.Write(simScan.BaseAddress() + 0x5000, planted, sizeof(planted)), "plant AOB");
    ModuleInfo simMod{"sim", simScan.BaseAddress(), game_layout::kSimulatedMemorySize};
    const uintptr_t hit = AobScanner::ScanModule(
        simScan, simMod, AobPattern::Parse("48 8B 0D ? ? ? ?"));
    CHECK(hit == simScan.BaseAddress() + 0x5000, "ScanModule phai tim AOB trong sim module");
    const auto hits = AobScanner::ScanModuleHits(
        simScan, simMod, AobPattern::Parse("48 8B 0D ? ? ? ?"), 4 * 1024 * 1024, 8);
    CHECK(!hits.empty() && hits[0] == simScan.BaseAddress() + 0x5000, "ScanModuleHits phai tra ve cung dia chi");

    std::cout << "  -> Parse / matching / wildcard / validation / ScanModule OK" << std::endl;
}

// ==========================================================
// 9. OffsetsStore (cache offset ra file TOML-subset)
// ==========================================================
void TestOffsetsStore() {
    std::cout << "[Test 9] OffsetsStore..." << std::endl;

    const std::string path = TestName("test_offsets.toml");
    DeleteFileA(path.c_str());

    {
        OffsetsStore s;
        s.Set("InGameState", 0x3E8);
        s.Set("CameraInstance", 0x16F8);
        CHECK(s.Save(path), "Save thất bại");
    }
    {
        OffsetsStore s;
        CHECK(s.Load(path), "Load thất bại");
        CHECK(s.Get("InGameState") == 0x3E8, "InGameState sai sau load");
        CHECK(s.Get("CameraInstance") == 0x16F8, "CameraInstance sai sau load");
        CHECK(s.Get("KhongTonTai", 0x42) == 0x42, "Default value sai");
        CHECK(!s.Has("KhongTonTai"), "Has() trả sai");
        s.Remove("CameraInstance");
        CHECK(!s.Has("CameraInstance"), "CameraInstance phải bị xóa bởi Remove()");
    }
    DeleteFileA(path.c_str());
    std::cout << "  -> Save / load cache offset đúng giá trị hex" << std::endl;
}

// ==========================================================
// 10. ReadProcessMemoryReader - tự gắn vào tiến trình test
// ==========================================================
void TestRpmSelfProcess() {
    std::cout << "[Test 10] ReadProcessMemory (self-process)..." << std::endl;

    ReadProcessMemoryReader reader;
    CHECK(reader.Attach(GetCurrentProcessId()), "Attach self thất bại");
    CHECK(reader.IsAttached(), "IsAttached sai");
    CHECK(reader.BackendName() != nullptr, "BackendName rỗng");

    // Đọc giá trị số nguyên
    int secret = 0x12345678;
    int out = 0;
    CHECK(reader.Read(reinterpret_cast<uintptr_t>(&secret), &out, sizeof(out)),
          "Read thất bại");
    CHECK(out == 0x12345678, "Giá trị đọc sai");

    // Đọc chuỗi
    char msg[] = "AutoPOE2-IPC-Test";
    char buf[32]{};
    CHECK(reader.Read(reinterpret_cast<uintptr_t>(msg), buf, sizeof(msg)),
          "Read chuỗi thất bại");
    CHECK(std::strcmp(buf, msg) == 0, "Chuỗi đọc không khớp");

    // Đọc địa chỉ không hợp lệ phải trả false
    CHECK(!reader.Read(0x1, &out, sizeof(out)), "Đọc địa chỉ không hợp lệ phải thất bại");

    // Giải pointer chain 1 cấp
    uintptr_t target = reinterpret_cast<uintptr_t>(&secret);
    uintptr_t* pTarget = &target;
    uintptr_t resolved = 0;
    CHECK(reader.DerefPointerChain(reinterpret_cast<uintptr_t>(pTarget), {0}, resolved),
          "DerefPointerChain thất bại");
    CHECK(resolved == target, "Pointer chain giải sai địa chỉ");

    reader.Detach();
    CHECK(!reader.IsAttached(), "Detach không hiệu lực");
    std::cout << "  -> Read / chuỗi / PointerChain / Detach OK" << std::endl;
}

// ==========================================================
// 12. SimulatedMemoryReader + EntityManager (game ảo end-to-end)
// ==========================================================
void TestSimulatedEntityManager() {
    std::cout << "[Test 12] Game ảo + EntityManager (pointer chain)..." << std::endl;

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize() && sim.Attach(game_layout::kSimulatedPid), "Khởi tạo hoặc Attach game ảo thất bại");
    CHECK(sim.InGame() != nullptr && sim.Player() != nullptr && sim.EntityMap() != nullptr,
          "Khối cấu trúc trỏ null");

    uint8_t scratch[4] = {};
    CHECK(!sim.Read(game_layout::kSimulatedBaseAddress + game_layout::kSimulatedMemorySize,
                    scratch, sizeof(scratch)),
          "Read ngoài phạm vi phải thất bại");

    auto make = [](uint32_t id, float x, float y, uint32_t hp, uint32_t flags) {
        game_layout::EntityMemory e{};
        e.magic = game_layout::kEntityMagic;
        e.id = id;
        e.type = game_layout::EntityTypeMonster;
        e.rarity = 0;
        e.posX = x; e.posY = y;
        e.currentHP = hp; e.maxHP = hp;
        e.extraFlags = flags | game_layout::kEntityFlagTargetable;
        std::snprintf(e.name, sizeof(e.name), "Entity_%u", id);
        return e;
    };

    bool allSpawned = (sim.SpawnEntity(make(1, 20, 15, 340, 0)) != UINT32_MAX)
                   && (sim.SpawnEntity(make(2, -35, 40, 520, 0)) != UINT32_MAX)
                   && (sim.SpawnEntity(make(3, 10, -80, 1400, 0)) != UINT32_MAX)
                   && (sim.SpawnEntity(make(4, -70, -20, 280, 0)) != UINT32_MAX)
                   && (sim.SpawnEntity(make(5, 200, 200, 2100, 0)) != UINT32_MAX)
                   && (sim.SpawnEntity(make(6, 50, 50, 999, game_layout::kEntityFlagDead)) != UINT32_MAX)
                   && (sim.SpawnEntity(make(7, -25, -25, 6500, game_layout::kEntityFlagBoss)) != UINT32_MAX);
    CHECK(allSpawned, "Spawn 7 entities thất bại");

    EntityManager mgr(sim);
    CHECK(mgr.Update(), "EntityManager.Update() thất bại trên game ảo");
    CHECK(mgr.Player().maxHP == 1250 && mgr.Player().maxMana == 300 &&
          mgr.AreaSeed() == 13361591 && mgr.TotalEntitiesInMap() == 7,
          "Dữ liệu Player hoặc AreaSeed sai");

    CHECK(mgr.Entities().size() == 5 && mgr.TotalEntitiesFiltered() == 2, "Số entity sau lọc sai");

    bool bossFound = false;
    for (const auto& e : mgr.Entities()) {
        if (e.id == 7 && (e.extraFlags & game_layout::kEntityFlagBoss) != 0 && e.maxHP == 6500) {
            bossFound = true;
        }
    }
    CHECK(bossFound, "Không tìm thấy boss ID 7 hợp lệ trong kết quả lọc");

    TelemetryPacket pkt{};
    mgr.FillTelemetry(pkt);
    CHECK(pkt.entityCount == 5 && pkt.player.maxHP == 1250, "pkt.entityCount hoặc maxHP sai");

    const uint32_t hpBefore = sim.Player()->currentHP;
    sim.Tick(0.008f);
    CHECK(sim.Player()->currentHP != hpBefore && mgr.Update() && mgr.Entities().size() == 5,
          "Tick biến đổi thế giới hoặc Update sau tick thất bại");

    const uint32_t savedMagic = sim.InGame()->magic;
    sim.InGame()->magic = 0xDEADBEEF;
    CHECK(!mgr.Update(), "Update phải thất bại khi magic InGameState sai");
    sim.InGame()->magic = savedMagic;

    std::cout << "  -> Đọc qua pointer chain, filter, snapshot, tick, gãy chuỗi OK" << std::endl;
}

// ==========================================================
// 13. PlayerFinder - auto-calibration qua pattern {HP, maxHP, maxHP}
// ==========================================================
void TestPlayerFinder() {
    std::cout << "[Test 13] PlayerFinder (findplayer pattern)..." << std::endl;

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize() && sim.Attach(game_layout::kSimulatedPid), "Khởi tạo hoặc Attach game ảo thất bại");

    auto writeTriple = [&](uintptr_t off, uint32_t hp, uint32_t mhp) {
        const uint32_t t[3] = {hp, mhp, mhp};
        std::memcpy(sim.Raw() + off, t, 12);
    };
    writeTriple(0x400000, 26, 42);
    writeTriple(0x401000, 10, 42);
    writeTriple(0x402000, 33, 40);

    PlayerFinder pf(sim);
    CHECK(pf.Scan(42) == 2 && pf.CandidateCount() == 2, "Scan phải tìm thấy 2 block");

    uint32_t hp = 0, mhp = 0;
    uint32_t direct = 0;
    CHECK(pf.Rescan(26) == 1 && pf.ReadStats(hp, mhp) && hp == 26 && mhp == 42 &&
          sim.ReadValue<uint32_t>(pf.Candidates()[0], direct) && direct == 26,
          "Rescan hoặc ReadStats thất bại");
    CHECK(pf.Rescan(41) == 0, "Rescan giá trị không có phải trả 0");

    const uint32_t poe2Life[4] = {1250, 1250, 1000, 0};
    std::memcpy(sim.Raw() + 0x410000, poe2Life, sizeof(poe2Life));
    uintptr_t autoFound = pf.AutoScanHP(sim.BaseAddress() + 0x410000, 1024 * 1024, 1250);
    CHECK(autoFound == sim.BaseAddress() + 0x410000, "AutoScanHP phải tìm thấy block LifeComponent POE2 khi +8 khác maxHP");

    const uint32_t glyphBuf[8] = {1500, 1500, 200, 0, 0, 0, 1501, 0};
    std::memcpy(sim.Raw() + 0x420000, glyphBuf, sizeof(glyphBuf));

    const uint32_t glyph4094[4] = {4094, 4094, 100, 0};
    std::memcpy(sim.Raw() + 0x430000, glyph4094, sizeof(glyph4094));

    const uint32_t highHpBuf[4] = {6500, 6500, 200, 0};
    std::memcpy(sim.Raw() + 0x438000, highHpBuf, sizeof(highHpBuf));

    const uint32_t ecsSig[2] = {2, 4};
    std::memcpy(sim.Raw() + 0x440000 - 8, ecsSig, sizeof(ecsSig));
    uint8_t lifeBlock[64] = {};
    uint32_t lifeStats[4] = {2150, 2150, 2150, 0};
    std::memcpy(lifeBlock, lifeStats, 16);
    const uint32_t lifeMana[2] = {300, 300};
    std::memcpy(lifeBlock + 24, lifeMana, 8);
    uint64_t fakeHeapPtr = sim.BaseAddress() + 0x100000;
    std::memcpy(lifeBlock + 0x20, &fakeHeapPtr, sizeof(fakeHeapPtr));
    std::memcpy(sim.Raw() + 0x440000, lifeBlock, sizeof(lifeBlock));

    const uint32_t memset255[8] = {255, 255, 255, 255, 255, 255, 255, 255};
    std::memcpy(sim.Raw() + 0x450000, memset255, sizeof(memset255));

    const uint32_t uniformBuf[8] = {1200, 1200, 0, 1200, 0, 0, 0, 0};
    std::memcpy(sim.Raw() + 0x460000, uniformBuf, sizeof(uniformBuf));

    bool garbageRejected = (pf.AutoScanHP(sim.BaseAddress() + 0x450000, 1024 * 1024, 255) == 0) &&
                          (pf.AutoScanHP(sim.BaseAddress() + 0x460000, 1024 * 1024, 1200) == 0) &&
                          (pf.AutoScanHP(sim.BaseAddress() + 0x430000, 0x8000, 0) == 0) &&
                          (pf.AutoScanHP(sim.BaseAddress() + 0x438000, 0x8000, 0) == 0);
    CHECK(garbageRejected, "AutoScanHP phải loại bỏ buffer 255/255, uniform, glyph 4094, >5000 unsig");

    uintptr_t autoDetectZero = pf.AutoScanHP(0, 0, 0);
    uint32_t foundHp = 0, foundMaxHp = 0;
    CHECK(autoDetectZero == sim.BaseAddress() + 0x440000 &&
          autoDetectZero != sim.BaseAddress() + 0x420000 &&
          autoDetectZero != sim.BaseAddress() + 0x430000 &&
          autoDetectZero != sim.BaseAddress() + 0x438000 &&
          autoDetectZero != sim.BaseAddress() + 0x450000 &&
          autoDetectZero != sim.BaseAddress() + 0x460000 &&
          sim.ReadValue<uint32_t>(autoDetectZero, foundHp) &&
          sim.ReadValue<uint32_t>(autoDetectZero + 4, foundMaxHp),
          "AutoScanHP(0, 0, 0) phải khóa chính xác LifeComponent ECS {2,4}");

    const uint32_t npcLife[8] = {1508, 1508, 1508, 0, 0, 0, 0, 0};
    std::memcpy(sim.Raw() + 0x470000, npcLife, sizeof(npcLife));
    uint8_t playerBlock[64] = {};
    const uint32_t playerLife[4] = {650, 650, 650, 0};
    std::memcpy(playerBlock, playerLife, 16);
    const uint32_t playerMana[2] = {300, 300};
    std::memcpy(playerBlock + 24, playerMana, 8);
    std::memcpy(sim.Raw() + 0x480000, playerBlock, sizeof(playerBlock));
    uintptr_t foundPlayer = pf.AutoScanHP(sim.BaseAddress() + 0x480000, 0x20000, 0);
    CHECK(foundPlayer == sim.BaseAddress() + 0x480000, "AutoScanHP phải ưu tiên Player LifeComponent hơn NPC 1508 HP");

    uint8_t minionBlock[64] = {};
    const uint32_t minionLife[4] = {503, 503, 470, 0};
    std::memcpy(minionBlock, minionLife, 16);
    const uint32_t minionMana[2] = {27, 27};
    std::memcpy(minionBlock + 24, minionMana, 8);
    std::memcpy(sim.Raw() + 0x490000, minionBlock, sizeof(minionBlock));

    uint8_t realPlayerBlock[64] = {};
    const uint32_t realPlayerLife[4] = {696, 696, 696, 0};
    std::memcpy(realPlayerBlock, realPlayerLife, 16);
    std::memcpy(realPlayerBlock + 24, playerMana, 8);
    std::memcpy(sim.Raw() + 0x4A0000, realPlayerBlock, sizeof(realPlayerBlock));

    uintptr_t disambigFound = pf.AutoScanHP(sim.BaseAddress() + 0x498000, 0x18000, 0);
    CHECK(disambigFound == sim.BaseAddress() + 0x4A0000 && disambigFound != sim.BaseAddress() + 0x490000,
          "AutoScanHP(0) BẮT BUỘC chọn Player (696HP/300Mana) thay vì Minion (503HP/27Mana)");

    std::cout << "  -> Scan/Rescan/ReadStats trên pattern {HP,42,42} và AutoScanHP(0,0,0) POE2 OK" << std::endl;
}

// ==========================================================
// 13b. PlayerFinder - XYZ Sanity & Validity Filter
// ==========================================================
void TestXYZSanityAndFiltering() {
    std::cout << "[Test 13b] PlayerFinder - XYZ Sanity & Validity Filter..." << std::endl;

    bool rejectedOk = !PlayerFinder::IsValidXYZ(9.3f, 9.3f, 9.3f) &&
                      !PlayerFinder::IsValidXYZ(1.0f, 1.0f, 1.0f) &&
                      !PlayerFinder::IsValidXYZ(0.0f, 0.0f, 0.0f) &&
                      !PlayerFinder::IsValidXYZ(NAN, 1000.0f, 200.0f) &&
                      !PlayerFinder::IsValidXYZ(1000.0f, INFINITY, 200.0f) &&
                      !PlayerFinder::IsValidXYZ(70000.0f, 2000.0f, 100.0f) &&
                      !PlayerFinder::IsValidXYZ(2000.0f, 3000.0f, 3500.0f) &&
                      !PlayerFinder::IsValidXYZ(104.109375f, 0.000000f, 104.109375f) &&
                      !PlayerFinder::IsValidXYZ(250.0f, 250.0f, 10.0f);
    CHECK(rejectedOk, "IsValidXYZ phải loại bỏ vector scale, 0, NaN/Inf, out-of-bounds, ma trận chiếu");

    bool acceptedOk = PlayerFinder::IsValidXYZ(5.0f, 4.0f, 0.0f) &&
                      PlayerFinder::IsValidXYZ(3814.08f, 7172.65f, 257.81f) &&
                      PlayerFinder::IsValidXYZ(-1500.5f, 4200.0f, -150.0f) &&
                      PlayerFinder::IsValidXYZ(100.0f, 15.0f, 50.0f) &&
                      PlayerFinder::IsValidXYZ(150.0f, 250.0f, 0.0000f) &&
                      PlayerFinder::IsValidXYZ(50.0f, 150.0f, 0.0000f) &&
                      PlayerFinder::IsValidXYZ(150.0f, 50.0f, 0.0000f) &&
                      PlayerFinder::IsValidXYZ(40.0f, 60.0f, 0.0000f);
    CHECK(acceptedOk, "IsValidXYZ phải chấp nhận tọa độ POE2 hợp lệ và địa hình phẳng Z=0");

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize() && sim.Attach(game_layout::kSimulatedPid), "Khởi tạo hoặc Attach sim thất bại");

    const uintptr_t base = sim.BaseAddress();
    float scaleTriple[3] = {9.3f, 9.3f, 9.3f};
    std::memcpy(sim.Raw() + 0x500000, scaleTriple, 12);
    float zeroTriple[3] = {0.0f, 0.0f, 0.0f};
    std::memcpy(sim.Raw() + 0x501000, zeroTriple, 12);
    float realTriple[3] = {3814.08f, 7172.65f, 257.81f};
    std::memcpy(sim.Raw() + 0x502000, realTriple, 12);

    PlayerFinder pf(sim);
    pf.ScanFloats(base + 0x501000, 64 * 1024, -10000.0f, 50000.0f);
    size_t triples = pf.FindXYZTriples();
    float rx = 0, ry = 0, rz = 0;
    CHECK(triples == 1 && pf.ReadPosition(rx, ry, rz) &&
          std::fabs(rx - 3814.08f) < 0.1f && std::fabs(ry - 7172.65f) < 0.1f && std::fabs(rz - 257.81f) < 0.1f,
          "FindXYZTriples hoặc ReadPosition thất bại");

    {
        SimulatedMemoryReader simFlat;
        CHECK(simFlat.Initialize() && simFlat.Attach(game_layout::kSimulatedPid), "Attach simFlat thất bại");

        const uintptr_t fBase = simFlat.BaseAddress();
        const uintptr_t playerPosAddr = fBase + 0x505000;
        float initialPos[3] = {12500.0f, 16800.0f, 150.0f};
        std::memcpy(simFlat.Raw() + 0x505000, initialPos, sizeof(initialPos));

        PlayerFinder pfFlat(simFlat);
        const size_t baseCount = pfFlat.SnapshotBaselineFloats(playerPosAddr, 64 * 1024);
        float movedPos[3] = {12525.0f, 16830.0f, 150.0f};
        std::memcpy(simFlat.Raw() + 0x505000, movedPos, sizeof(movedPos));

        const size_t movedCount = pfFlat.FindMovedFromBaseline(0.8f, 5000.0f);
        const size_t triplesFound = pfFlat.FindXYZTriples();
        float curX = 0, curY = 0, curZ = 0;
        CHECK(baseCount > 0 && baseCount < 5000 && movedCount >= 2 && triplesFound >= 1 &&
              pfFlat.XYZAddresses()[0] == playerPosAddr && pfFlat.ReadPosition(curX, curY, curZ) &&
              std::fabs(curX - 12525.0f) < 0.1f && std::fabs(curY - 16830.0f) < 0.1f && std::fabs(curZ - 150.0f) < 0.1f,
              "Flat ground movement detection failed");

        float singleAxisPos[3] = {12550.0f, 16830.0f, 150.0f};
        std::memcpy(simFlat.Raw() + 0x505000, singleAxisPos, sizeof(singleAxisPos));
        const size_t singleMoved = pfFlat.FindMovedFromBaseline(0.8f, 5000.0f);
        const size_t singleTriples = pfFlat.FindXYZTriples();
        CHECK(singleMoved >= 1 && singleTriples >= 1 && pfFlat.XYZAddresses()[0] == playerPosAddr &&
              pfFlat.ReadPosition(curX, curY, curZ) && std::fabs(curX - 12550.0f) < 0.1f,
              "Single-axis movement detection failed");
    }

    std::cout << "  -> Dynamic XYZ filter & Single-Axis displacement regression OK!" << std::endl;
}

// ==========================================================
// 18. AutoPositionScan (Zero User Input từ LogSensor events)
// ==========================================================
void TestAutoPositionScan() {
    std::cout << "[Test 18] AutoPositionScan - Zero User Input tu LogSensor..." << std::endl;

    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "GameSession Init sim that bai");

    // 1. Gia lap su kien chuyen canh
    session.OnZoneSceneChanged("The Riverbank");
    CHECK(session.AutoScanState() == GameSession::AutoPositionState::WAITING_ZONE_LOAD, "State phai la WAITING_ZONE_LOAD");

    // 1b. Kiem tra OnZoneSceneChanged: Bo qua khi newScene rong, (null), hoac trung map hien tai
    uintptr_t testPlayerAddr = 0x12345678;
    session.SetPlayerAddress(testPlayerAddr);
    TownQuestEngine dummyTqe;
    dummyTqe.StartTravel(1, 0, 1000);
    session.SetTownQuestEngine(&dummyTqe);

    // Chuyen canh UI Overlay World Map & bo qua cac canh khong hop le:
    constexpr const char* kUiScenes[] = {
        "", "(null)", "The Riverbank", "Act 1", "Act 2", "Act 3", "Act 4", "Atlas", "(unknown)", "[DNT] World Map"
    };
    bool uiOverlayIgnored = true;
    for (const char* scene : kUiScenes) {
        session.OnZoneSceneChanged(scene);
        if (session.PlayerAddress() != testPlayerAddr || !dummyTqe.IsActive()) {
            uiOverlayIgnored = false;
            break;
        }
    }
    CHECK(uiOverlayIgnored, "OnZoneSceneChanged phai bo qua toan bo cac canh UI overlay/trung map ma khong reset state");

    // Kiem tra ham tinh GameSession::IsUIOverlayScene
    bool isUiOk = true;
    for (const char* scene : {"Act 1", "Act 2", "Act 3", "Act 4", "Atlas", "(null)", "(unknown)", "[DNT] Debug Scene"}) {
        if (!GameSession::IsUIOverlayScene(scene)) { isUiOk = false; break; }
    }
    CHECK(isUiOk && !GameSession::IsUIOverlayScene("The Riverbank"), "IsUIOverlayScene nhan dien chinh xac cac UI overlay scene va gameplay scene");


    // Chuyen canh sang map moi that su ("The Ogham Farmlands"):
    session.OnZoneSceneChanged("The Ogham Farmlands");
    CHECK(session.PlayerAddress() == 0, "OnZoneSceneChanged sang map moi phai reset playerAddr = 0");
    CHECK(!dummyTqe.IsActive(), "OnZoneSceneChanged sang map moi phai reset TownQuestEngine");

    // Reset lai Riverbank de tiep tuc cac buoc test tiep theo
    session.OnZoneSceneChanged("The Riverbank");

    // 2. Gia lap tai man hinh xong (dung yen tai cong vao)
    session.OnZoneLoadingFinished("The Riverbank");
    CHECK(session.AutoScanState() == GameSession::AutoPositionState::CAPTURING_STILL_BASELINE, "State phai la CAPTURING_STILL_BASELINE");

    std::this_thread::sleep_for(std::chrono::milliseconds(300));
    session.ProcessAutoPositionScan();
    CHECK(session.AutoScanState() == GameSession::AutoPositionState::WAITING_FIRST_STEP, "State phai la WAITING_FIRST_STEP");

    // 3. Dung yen -> van o WAITING_FIRST_STEP
    session.ProcessAutoPositionScan();
    CHECK(session.AutoScanState() == GameSession::AutoPositionState::WAITING_FIRST_STEP, "Dung yen thi phai tiep tuc cho");

    // 4. Kiem tra IsValidLifeComponent
    const uintptr_t hpAddr = game_layout::kSimulatedBaseAddress + game_layout::kPlayerStateOffset + 16;
    CHECK(session.IsValidLifeComponent(hpAddr, false), "IsValidLifeComponent với địa chỉ hợp lệ phải trả true");
    session.SetExpectedMaxHP(500);
    CHECK(!session.IsValidLifeComponent(hpAddr, false), "IsValidLifeComponent phải trả false khi maxHP không khớp expectedMaxHP");
    session.SetExpectedMaxHP(1250);
    CHECK(session.IsValidLifeComponent(hpAddr, true), "IsValidLifeComponent với expectedMaxHP=1250 khớp cả cur và max phải trả true");
    session.SetExpectedMaxHP(0);

    std::cout << "  -> Zero-User-Input auto-scan state machine & IsValidLifeComponent OK" << std::endl;
}

void TestAutoDetectManaPool() {
    std::cout << "[Test 19] AutoDetectManaPool around PlayerState..." << std::endl;

    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "GameSession Init sim that bai");

    const uintptr_t hpAddr = game_layout::kSimulatedBaseAddress + game_layout::kPlayerStateOffset + 16;
    session.SetPlayerAddress(hpAddr);
    CHECK(!session.ManaCalibrated(), "Ban dau mana chua calibrate");

    bool detected = session.AutoDetectManaPool(300);
    CHECK(detected, "AutoDetectManaPool phai thanh cong voi expectedMaxMana=300");
    CHECK(session.ManaCalibrated(), "Sau khi detect thanh cong thi ManaCalibrated phai true");
    CHECK(session.ManaAddress() == hpAddr + 8, "Dia chi mana phai o hpAddr + 8");

    // Kiểm thử tự động dò tìm khi expectedMaxMana = 0 (Zero-Input Auto-Detection)
    session.ResetManaAddress();
    CHECK(!session.ManaCalibrated(), "Mana phai uncalibrated sau khi reset");
    bool zeroInputDetected = session.AutoDetectManaPool(0);
    CHECK(zeroInputDetected, "AutoDetectManaPool(0) phai tu dong phat hien resource pool hop le");
    CHECK(session.ManaAddress() == hpAddr + 8, "Zero-Input detect phai khoa dung dia chi mana");

    std::cout << "  -> AutoDetectManaPool memory probe (explicit & zero-input) OK" << std::endl;
}

void TestTerrainReader() {
    std::cout << "[Test 23] TerrainReader & Native Terrain Integration..." << std::endl;

    constexpr uint32_t kCols = 80;
    constexpr uint32_t kRows = 60;
    std::vector<uint8_t> rawMelee(kCols * kRows, 1);
    for (uint32_t y = 10; y <= 50; ++y) {
        rawMelee[y * kCols + 30] = 0;
    }

    std::vector<uint8_t> decoded;
    bool okDecode = memory::TerrainReader::DecodeWalkableBuffer(
        rawMelee.data(), rawMelee.size(), kCols, kRows, kCols, decoded
    );
    bool decodeOk = okDecode && (decoded.size() == kCols * kRows) &&
                    (decoded[20 * kCols + 30] == 0) && (decoded[20 * kCols + 29] == 1);
    CHECK(decodeOk, "DecodeWalkableBuffer failed");

    memory::NativeTerrainData nativeMap;
    nativeMap.cols = kCols;
    nativeMap.rows = kRows;
    nativeMap.bytesPerRow = kCols;
    nativeMap.gridToWorldScale = 10.0f;
    nativeMap.worldOriginX = 0.0f;
    nativeMap.worldOriginY = 0.0f;
    nativeMap.walkability = decoded;

    uint32_t gx = 0, gy = 0;
    float wx = 0.0f, wy = 0.0f;
    bool nativeOk = nativeMap.IsValid() && nativeMap.IsWalkableGrid(29, 20) && !nativeMap.IsWalkableGrid(30, 20) &&
                    nativeMap.WorldToGrid(295.0f, 205.0f, gx, gy) && (gx == 29 && gy == 20) &&
                    nativeMap.GridToWorld(29, 20, wx, wy) && (std::abs(wx - 295.0f) < 1.0f && std::abs(wy - 205.0f) < 1.0f) &&
                    !nativeMap.IsWalkableWorld(305.0f, 205.0f) && nativeMap.IsWalkableWorld(295.0f, 205.0f) &&
                    (nativeMap.WalkablePercentage() > 80.0f && nativeMap.WalkablePercentage() < 100.0f);
    CHECK(nativeOk, "NativeTerrainData grid/world conversion and walkability checks failed");

    navigation::TerrainGrid grid(6.0f);
    bool gridLoaded = grid.LoadFromNativeTerrain(nativeMap, 200.0f, 200.0f) && grid.HasNativeTerrain() &&
                      !grid.IsWalkable(305.0f, 205.0f) && grid.IsWalkable(295.0f, 205.0f) &&
                      !grid.HasLineOfSight(200.0f, 200.0f, 400.0f, 200.0f);
    CHECK(gridLoaded, "Grid LoadFromNativeTerrain and LoS obstruction failed");

    navigation::Pathfinder pathfinder;
    auto path = pathfinder.FindPath({200.0f, 200.0f}, {400.0f, 200.0f}, grid);
    CHECK(!path.empty(), "A* phải tìm được đường vòng qua bức tường native");

    std::cout << "  -> TerrainReader & Native Terrain Integration OK" << std::endl;
}

// ==========================================================
// 30. Test Chunk Boundary Overlap Regression (Rule 11)
// ==========================================================
void TestChunkBoundaryOverlapRegression() {
    std::cout << "[Test 30] Chunk Boundary Overlap Regression (Rule 11)..." << std::endl;

    // Giả lập vùng bộ nhớ kích thước 2048 bytes gồm 2 chunk (mỗi chunk 1024 bytes)
    const size_t kChunkSize = 1024;
    std::vector<uint8_t> memory(kChunkSize * 2, 0x00);

    // Mẫu dữ liệu 12-byte giả lập cấu trúc {float x, float y, float z} hoặc AOB pattern
    // Đặt mẫu bắt đầu tại offset 1020 (vắt ngang biên: 4 byte ở chunk 1 [1020..1023], 8 byte ở chunk 2 [1024..1031])
    const size_t kTargetOffset = 1020;
    const uint8_t pattern12B[12] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 };
    std::memcpy(&memory[kTargetOffset], pattern12B, sizeof(pattern12B));

    // Case 1: Quét không có overlap (LỖI CŨ CỦA PHIÊN BẢN TRƯỚC)
    // Bước nhảy cố định = kChunkSize
    bool foundWithoutOverlap = false;
    for (size_t offset = 0; offset < memory.size(); offset += kChunkSize) {
        size_t bytesToScan = (std::min)(kChunkSize, memory.size() - offset);
        if (bytesToScan < sizeof(pattern12B)) continue;
        for (size_t i = 0; i <= bytesToScan - sizeof(pattern12B); ++i) {
            if (std::memcmp(&memory[offset + i], pattern12B, sizeof(pattern12B)) == 0) {
                foundWithoutOverlap = true;
                break;
            }
        }
        if (foundWithoutOverlap) break;
    }
    CHECK(!foundWithoutOverlap, "Tái hiện lỗi: Quét không có overlap phải bỏ lỡ pattern vắt ngang biên");

    // Case 2: Quét có overlap 12-byte (BẢN VÁ MỚI THEO RULE 8 & 11)
    const size_t kOverlap12 = 12;
    std::vector<size_t> foundOffsets12;

    for (size_t offset = 0; offset < memory.size(); ) {
        size_t bytesToScan = (std::min)(kChunkSize, memory.size() - offset);
        if (bytesToScan >= sizeof(pattern12B)) {
            for (size_t i = 0; i <= bytesToScan - sizeof(pattern12B); ++i) {
                if (std::memcmp(&memory[offset + i], pattern12B, sizeof(pattern12B)) == 0) {
                    size_t globalAddr = offset + i;
                    if (foundOffsets12.empty() || foundOffsets12.back() != globalAddr) {
                        foundOffsets12.push_back(globalAddr);
                    }
                }
            }
        }
        if (offset + kChunkSize >= memory.size()) break;
        offset += (kChunkSize - kOverlap12);
    }
    CHECK(foundOffsets12.size() == 1, "Quét có overlap phải tìm thấy chính xác 1 pattern");
    CHECK(!foundOffsets12.empty() && foundOffsets12[0] == kTargetOffset, "Offset phát hiện phải khớp chính xác 1020");

    // Case 3: Quét float 4-byte nằm tại offset 1022 với kOverlap = 4
    float targetFloat = 12345.67f;
    const size_t kFloatOffset = 1022; // 2 byte trong chunk 1, 2 byte trong chunk 2
    std::memcpy(&memory[kFloatOffset], &targetFloat, sizeof(float));

    const size_t kOverlap4 = 4;
    std::vector<size_t> foundFloatOffsets;
    for (size_t offset = 0; offset < memory.size(); ) {
        size_t bytesToScan = (std::min)(kChunkSize, memory.size() - offset);
        if (bytesToScan >= sizeof(float)) {
            for (size_t i = 0; i <= bytesToScan - sizeof(float); ++i) {
                if (std::memcmp(&memory[offset + i], &targetFloat, sizeof(float)) == 0) {
                    size_t globalAddr = offset + i;
                    if (foundFloatOffsets.empty() || foundFloatOffsets.back() != globalAddr) {
                        foundFloatOffsets.push_back(globalAddr);
                    }
                }
            }
        }
        if (offset + kChunkSize >= memory.size()) break;
        offset += (kChunkSize - kOverlap4);
    }
    CHECK(foundFloatOffsets.size() == 1, "Quét float với kOverlap=4 phải tìm thấy chính xác 1 kết quả");
    CHECK(!foundFloatOffsets.empty() && foundFloatOffsets[0] == kFloatOffset, "Float offset phát hiện phải là 1022");

    std::cout << "  -> Reproduction of chunk-boundary miss & overlap 12B/4B fix verified PASS" << std::endl;
}

// ==========================================================
// 32. Test Cross-Vital Validation & Stale Offset Regression (Rule 8 & 11)
// ==========================================================
void TestCrossVitalValidationAndStaleOffsetRegression() {
    std::cout << "[Test 32] Cross-Vital Validation & Anti-778 Bug Regression..." << std::endl;

    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "GameSession Init sim thất bại");

    auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
    PlayerFinder pf(*sim);

    const uintptr_t bug778Addr = sim->BaseAddress() + 0x490000;
    const uint32_t bug778Stats[8] = { 778, 778, 200, 32763, 654, 0, 779, 779 };
    std::memcpy(sim->Raw() + 0x490000, bug778Stats, sizeof(bug778Stats));

    const uintptr_t monsterAddr = sim->BaseAddress() + 0x495000;
    const uint32_t monsterStats[8] = { 340, 340, 0, 0, 0, 0, 0, 0 };
    std::memcpy(sim->Raw() + 0x495000, monsterStats, sizeof(monsterStats));

    bool garbageRejected = !session.IsValidLifeComponent(bug778Addr, false) &&
                           (pf.AutoScanHP(bug778Addr, 0x1000, 0) == 0) &&
                           !session.IsValidLifeComponent(monsterAddr, false) &&
                           (pf.AutoScanHP(monsterAddr, 0x1000, 0) == 0);
    CHECK(garbageRejected, "Garbage 778/778 and monster without mana must be rejected");

    const uintptr_t realPlayerAddr = sim->BaseAddress() + 0x4A0000;
    const uint32_t realPlayerStats[8] = { 495, 503, 503, 105, 200, 0, 344, 344 };
    std::memcpy(sim->Raw() + 0x4A0000, realPlayerStats, sizeof(realPlayerStats));

    session.SetExpectedMaxHP(0);
    bool autoOk = session.IsValidLifeComponent(realPlayerAddr, false);
    session.SetExpectedMaxHP(503);
    bool exactOk = session.IsValidLifeComponent(realPlayerAddr, false);
    session.SetExpectedMaxHP(500);
    bool mismatchRejected = !session.IsValidLifeComponent(realPlayerAddr, false);
    session.SetExpectedMaxHP(0);
    CHECK(autoOk && exactOk && mismatchRejected, "Real player cross-vital validation mismatch");

    const std::string testTomlPath = TestName("test_offsets_stale_heap.toml");
    DeleteFileA(testTomlPath.c_str());

    {
        OffsetsStore s;
        bool allRejected = !s.Set("player_addr", 0x269524f0270ULL) &&
                           !s.Set("xyz_addr", 0x140110000ULL) &&
                           !s.Set("shield_addr", 0x269524f027CULL) &&
                           !s.Set("mana_addr", 0x269524f0288ULL) &&
                           !s.Set("camera_addr", 0xb25bdeee80ULL) &&
                           !s.Set("fog_density_addr", 0xb251df218cULL);
        CHECK(allRejected && s.RejectedOnSetCount() == 6 && !s.Has("player_addr") && !s.Has("xyz_addr") &&
              s.Set("entity_map_offset", 0x3E8) && s.Save(testTomlPath),
              "INV-OFFSET-01: Set() rejection of heap keys failed");
    }

    {
        std::ifstream rawFile(testTomlPath);
        std::string content, rawLine;
        while (std::getline(rawFile, rawLine)) {
            const auto hashPos = rawLine.find('#');
            if (hashPos != std::string::npos) rawLine.erase(hashPos);
            content += rawLine + "\n";
        }
        CHECK(content.find("player_addr") == std::string::npos &&
              content.find("xyz_addr") == std::string::npos &&
              content.find("shield_addr") == std::string::npos &&
              content.find("entity_map_offset") != std::string::npos,
              "Offsets file on disk must not contain heap keys");
    }

    {
        const std::string legacyPath = TestName("test_offsets_legacy_polluted.toml");
        DeleteFileA(legacyPath.c_str());
        {
            std::ofstream legacy(legacyPath);
            legacy << "# Legacy polluted file\nplayer_addr = 0x269524f0270\nxyz_addr = 0x140110000\nspirit_addr = 0x269524f0290\ncamera_addr = 0xb25bdeee80\nentity_map_offset = 0x3e8\naob_pattern_1 = 48 8B 05 ? ? ? ? 48 8B 40 38\n";
        }
        OffsetsStore s;
        CHECK(s.Load(legacyPath) && s.RejectedOnLoadCount() == 4 &&
              !s.Has("player_addr") && !s.Has("xyz_addr") && !s.Has("spirit_addr") && !s.Has("camera_addr") &&
              s.Get("entity_map_offset") == 0x3E8 && s.GetString("aob_pattern_1") == "48 8B 05 ? ? ? ? 48 8B 40 38",
              "INV-OFFSET-01: Load() sanitization of legacy heap keys failed");
        DeleteFileA(legacyPath.c_str());
    }
    DeleteFileA(testTomlPath.c_str());

    std::cout << "  -> Cross-Vital Validation & Anti-778 Bug Regression OK" << std::endl;
}

// ==========================================================
// 33. Test XYZ 2D Planar Movement Regression (Sandswept Marsh Swamp) (Rule 8 & 11)
//  - Tái hiện sự cố ngày 07/09/2026: Khi nhân vật chạy trên đầm lầy Sandswept Marsh,
//    tọa độ X và Y thay đổi (delta >= 0.8f), trong khi Z là hằng số độ cao mặt đất (delta == 0.0f).
//  - Thuật toán XYZ scanner BẮT BUỘC PHẢI NHẬN DIỆN THÀNH CÔNG Vector3 Position!
// ==========================================================
void TestXYZ2DPlanarMovementRegression() {
    std::cout << "[Test 33] XYZ 2D Planar Movement Regression (Sandswept Marsh Swamp)..." << std::endl;

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize(), "Khởi tạo game ảo thất bại");
    CHECK(sim.Attach(game_layout::kSimulatedPid), "Attach thất bại");

    const uintptr_t base = sim.BaseAddress();
    const uintptr_t posAddr = base + 0x510000;

    // 1. Ghi tọa độ ban đầu tại posAddr: X=1500.0f, Y=3200.0f, Z=125.5f (độ cao đầm lầy)
    float initialPos[3] = {1500.0f, 3200.0f, 125.5f};
    std::memcpy(sim.Raw() + 0x510000, initialPos, 12);

    PlayerFinder pf(sim);
    size_t baselineCount = pf.SnapshotBaselineFloats(posAddr, 64 * 1024);
    CHECK(baselineCount >= 3, "Baseline floats phải có ít nhất 3 float");

    // 2. Nhân vật chạy trên đầm lầy:
    // X đổi từ 1500.0f -> 1525.0f (+25.0f >= 0.8f)
    // Y đổi từ 3200.0f -> 3230.0f (+30.0f >= 0.8f)
    // Z GIỮ NGUYÊN HOÀN TOÀN: 125.5f -> 125.5f (delta == 0.0f!)
    float movedPos[3] = {1525.0f, 3230.0f, 125.5f};
    std::memcpy(sim.Raw() + 0x510000, movedPos, 12);

    // Tìm các float đã di chuyển: Chỉ X và Y có delta >= 0.8f, Z có delta = 0.0f
    size_t moved = pf.FindMovedFromBaseline(0.8f, 5000.0f);
    CHECK(moved >= 2, "Phải phát hiện ít nhất 2 float di chuyển (X và Y)");

    // Thuật toán FindXYZTriples BẮT BUỘC PHẢI nhận diện thành công Vector3 Position!
    size_t triples = pf.FindXYZTriples();
    CHECK(triples >= 1, "FindXYZTriples PHẢI tìm được ít nhất 1 triple dù Z không đổi!");
    CHECK(!pf.XYZAddresses().empty() && pf.XYZAddresses().front() == posAddr, "Địa chỉ triple phải khớp posAddr 0x510000");

    float rx = 0.0f, ry = 0.0f, rz = 0.0f;
    CHECK(pf.ReadPosition(rx, ry, rz), "ReadPosition phải đọc thành công tọa độ");
    CHECK(std::fabs(rx - 1525.0f) < 0.01f, "Tọa độ X phải là 1525.0f");
    CHECK(std::fabs(ry - 3230.0f) < 0.01f, "Tọa độ Y phải là 3230.0f");
    CHECK(std::fabs(rz - 125.5f) < 0.01f, "Tọa độ Z phải là 125.5f (bảo toàn độ cao đầm lầy)");

    // 3. Kiểm tra trường hợp đối xứng: Leo dốc (Y và Z đổi, X đứng yên)
    float slopePos[3] = {1525.0f, 3255.0f, 145.0f};
    std::memcpy(sim.Raw() + 0x510000, slopePos, 12);

    size_t movedSlope = pf.FindMovedFromBaseline(0.8f, 5000.0f);
    CHECK(movedSlope >= 2, "Phải phát hiện ít nhất 2 float di chuyển trên dốc (Y và Z)");

    size_t triplesSlope = pf.FindXYZTriples();
    CHECK(triplesSlope >= 1, "FindXYZTriples PHẢI tìm được triple khi leo dốc Y-Z!");

    float sx = 0.0f, sy = 0.0f, sz = 0.0f;
    CHECK(pf.ReadPosition(sx, sy, sz), "ReadPosition dốc phải thành công");
    CHECK(std::fabs(sx - 1525.0f) < 0.01f, "Tọa độ X dốc phải là 1525.0f");
    CHECK(std::fabs(sy - 3255.0f) < 0.01f, "Tọa độ Y dốc phải là 3255.0f");
    CHECK(std::fabs(sz - 145.0f) < 0.01f, "Tọa độ Z dốc phải là 145.0f");

    std::cout << "  -> 2D planar (swamp Z=const) and slope movement XYZ detection OK" << std::endl;
}

void TestVitalsStructuralRejection() {
    std::cout << "[Test 33] Structural Fake-LifeComponent + ES/Spirit invariant regression..." << std::endl;

    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "GameSession Init sim that bai");
    auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
    CHECK(sim != nullptr, "sim reader phải khác null");

    const uintptr_t hpAddr = game_layout::kSimulatedBaseAddress + game_layout::kPlayerStateOffset + 16;

    // ===== 1. Block giả THẬT từ log client 19:26/07-09 (biến thể có pointer hợp lệ
    //     + ECS signature {2,4} mà bản vá magic "778/778" cũ KHÔNG chặn được):
    //     {778, 778, 1, 32763(0x7FFB), 54, 617, ptr64, 617, ...}
    //     Discriminator: +12 = ES cur = 32763 > 20000 mà không có Mana/pointer chứng thực.
    const uintptr_t fakeBase = sim->BaseAddress() + 0x4B0000;
    uint8_t fakeBlock[128] = {};
    const uint32_t fakePrefix[2] = { 2, 4 }; // [-8], [-4] ECS LifeComponent signature
    std::memcpy(fakeBlock, fakePrefix, sizeof(fakePrefix));
    const uint32_t fakeStats[11] = {
        778, 778,        // +0, +4  HP/MaxHP (giống UI glyph 778)
        1,               // +8      unreservedHP VÔ LÝ (vi phạm hp <= unreserved <= maxHP)
        32763, 54,       // +12, +16 garbage (0x7FFB)
        617,             // +20
        0, 0,            // +24, +28 (ghi pointer 64-bit bên dưới)
        617,             // +32
        0, 0             // +36, +40
    };
    std::memcpy(fakeBlock + 8, fakeStats, sizeof(fakeStats));
    const uint64_t fakePtr = sim->BaseAddress() + 0x4B1000;
    std::memcpy(fakeBlock + 8 + 24, &fakePtr, 8); // con trỏ "hợp lệ" tại +24 của block
    std::memcpy(sim->Raw() + 0x4B0000, fakeBlock, sizeof(fakeBlock));

    const uintptr_t fakeLcAddr = fakeBase + 8;
    CHECK(!session.IsValidLifeComponent(fakeLcAddr, false),
          "Block giả 778/778 biến thể pointer BẮT BUỘC bị loại bởi bất biến ES cur <= 20000");

    // AutoScanHP ở chế độ Auto phải loại bỏ block giả ngay tầng scan
    // (AutoDetectPlayerHP no-op trên backend SIM nên test qua PlayerFinder)
    PlayerFinder pf(*sim);
    CHECK(pf.AutoScanHP(fakeLcAddr, 0x1000, 0) == 0,
          "AutoScanHP(0,0,0) BẮT BUỘC loại block giả 778/778 biến thể pointer (ES cur 0x7FFB)");

    // ===== 2. Bất biến ES: IsValidShieldAddress từ chối cặp đảo ngược/rác =====
    const uintptr_t esGood = sim->BaseAddress() + 0x4B2000;
    const uintptr_t esBad  = sim->BaseAddress() + 0x4B2100;
    const uint32_t goodPair[2] = { 105, 200 };   // {cur, max} hợp lệ
    const uint32_t badPair[2]  = { 32763, 54 };  // cặp rác đảo ngược từ log thật
    std::memcpy(sim->Raw() + 0x4B2000, goodPair, sizeof(goodPair));
    std::memcpy(sim->Raw() + 0x4B2100, badPair, sizeof(badPair));
    CHECK(session.IsValidShieldAddress(esGood), "ES {105,200} (cur<=max) phải được chấp nhận");
    CHECK(!session.IsValidShieldAddress(esBad), "ES đảo ngược {32763,54} (cur>max) BẮT BUỘC bị từ chối");

    // ===== 3. Spirit: phát hiện quét sau khi Mana khóa, đúng HPbase+24 trên sim =====
    session.SetPlayerAddress(hpAddr); // độc lập với kết quả AutoDetectPlayerHP ở trên
    CHECK(session.AutoDetectManaPool(300), "Mana 300 phải được khóa trên sim trước khi dò Spirit");
    CHECK(session.AutoDetectSpirit(0), "AutoDetectSpirit phải khóa spirit 100/100 trên layout sim");
    CHECK(session.SpiritAddress() == hpAddr + 24, "Spirit phải nằm tại HPbase + 24 (PlayerStateMemory)");

    std::cout << "  -> Structural vitals rejection + ES/Spirit invariants OK" << std::endl;
}

// ==========================================================
// 39. Vitals Scoring, Dynamic Level-Up Adaptation & Multi-Vital Harvesting (Rule 8, 11, 14)
// ==========================================================
void TestVitalsScoringAndDynamicAdaptation() {
    std::cout << "[Test 39] Vitals Scoring, Dynamic Level-Up & Multi-Vital Harvesting..." << std::endl;

    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "GameSession Init sim that bai");

    auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
    const uintptr_t minionAddr = sim->BaseAddress() + 0x4C0000;
    const uintptr_t playerAddr = sim->BaseAddress() + 0x4C1000;
    const uintptr_t shieldAddr = sim->BaseAddress() + 0x4C2000;
    const uintptr_t manaAddr   = sim->BaseAddress() + 0x4C3000;

    uint8_t minionBlock[64] = {};
    const uint32_t minionStats[8] = { 93, 93, 93, 0, 0, 0, 0, 0 };
    std::memcpy(minionBlock, minionStats, sizeof(minionStats));
    std::memcpy(sim->Raw() + 0x4C0000, minionBlock, sizeof(minionBlock));

    const uint32_t ecsSig[2] = {2, 4};
    std::memcpy(sim->Raw() + 0x4C1000 - 8, ecsSig, sizeof(ecsSig));
    uint8_t playerBlock[64] = {};
    const uint32_t playerStats[8] = { 793, 793, 1, 0, 0, 0, 741, 741 };
    std::memcpy(playerBlock, playerStats, sizeof(playerStats));
    std::memcpy(sim->Raw() + 0x4C1000, playerBlock, sizeof(playerBlock));

    uint8_t shieldBlock[32] = {};
    const uint32_t shieldStats[4] = {204, 317, 0, 0};
    std::memcpy(shieldBlock, shieldStats, sizeof(shieldStats));
    std::memcpy(sim->Raw() + 0x4C2000, shieldBlock, sizeof(shieldBlock));

    uint8_t manaBlock[32] = {};
    const uint32_t manaStats[4] = {550, 550, 0, 0};
    std::memcpy(manaBlock, manaStats, sizeof(manaStats));
    std::memcpy(sim->Raw() + 0x4C3000, manaBlock, sizeof(manaBlock));

    PlayerFinder pf(*sim);
    uintptr_t scanResult = pf.AutoScanHP(playerAddr, 0x10000, 0);

    const auto& shieldCands = pf.ShieldCandidates();
    bool foundShieldCand = false;
    for (const auto& c : shieldCands) {
        if (c.addr == shieldAddr && c.cur == 204 && c.max == 317) {
            foundShieldCand = true;
            break;
        }
    }

    const auto& manaCands = pf.ManaCandidates();
    bool foundManaCand = false;
    for (const auto& c : manaCands) {
        if (c.addr == manaAddr || c.max == 741 || c.cur == 741 || c.max == 550) {
            foundManaCand = true;
            break;
        }
    }

    CHECK(scanResult == playerAddr && scanResult != minionAddr && !shieldCands.empty() && foundShieldCand && !manaCands.empty() && foundManaCand,
          "AutoScanHP scoring and multi-vital harvesting failed");

    const uint32_t nearShield[2] = {204, 317};
    std::memcpy(sim->Raw() + 0x4C1000 + 0x40, nearShield, sizeof(nearShield));

    session.SetPlayerAddress(playerAddr);
    session.SetExpectedMaxHP(793);
    session.SetExpectedMaxES(0);

    bool esLocked = session.AutoDetectShield(0);
    uint32_t readEsCur = 0, readEsMax = 0;
    bool esOk = esLocked && (session.ShieldAddress() == playerAddr + 0x40) &&
                sim->ReadValue<uint32_t>(session.ShieldAddress(), readEsCur) &&
                sim->ReadValue<uint32_t>(session.ShieldAddress() + 4, readEsMax) &&
                (readEsCur == 204 && readEsMax == 317);
    CHECK(esOk, "AutoDetectShield damaged shield 204/317 failed");

    bool initHpOk = (session.PlayerAddress() == playerAddr) && (session.ExpectedMaxHP() == 793);
    const uint32_t levelUpHp[2] = {820, 820};
    std::memcpy(sim->Raw() + 0x4C1000, levelUpHp, sizeof(levelUpHp));
    bool validLife = session.IsValidLifeComponent(playerAddr, false);
    bool adapted = session.AutoDetectPlayerHP(793);
    bool adaptOk = initHpOk && validLife && adapted &&
                   (session.PlayerAddress() == playerAddr) && (session.ExpectedMaxHP() == 820);
    CHECK(adaptOk, "Dynamic Level-Up / Gear change adaptation failed");

    std::cout << "  -> Vitals Scoring, Dynamic Level-Up & Multi-Vital Harvesting OK" << std::endl;
}

// ==========================================================
// 41. Chaos Inoculation (CI) Build Invariants & Reflexes
// ==========================================================
void TestChaosInoculationCiBuild() {
    std::cout << "[Test 41] Chaos Inoculation (CI) Build Invariants & Reflexes..." << std::endl;

    // 1. Kiểm tra session.IsValidLifeComponent chấp nhận block CI có maxHP = 1, hp = 1, ECS prefix {2, 4}, ES cur/max hợp lệ
    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "GameSession Init sim that bai");
    auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
    CHECK(sim != nullptr, "sim reader phai khac null");

    const uintptr_t ciBase = sim->BaseAddress() + 0x4D0000;
    uint8_t ciBlock[128] = {};
    const uint32_t ciPrefix[2] = { 2, 4 }; // ECS signature [-8], [-4]
    std::memcpy(ciBlock, ciPrefix, sizeof(ciPrefix));
    const uint32_t ciStats[11] = {
        1, 1,           // +0 HP, +4 MaxHP (CI build)
        1,              // +8 unreservedHP
        5000, 5000,     // +12, +16 ES {cur, max} hợp lệ
        0,              // +20
        300, 300,       // +24, +28 Mana {cur, max}
        0, 0, 0         // +32, +36, +40
    };
    std::memcpy(ciBlock + 8, ciStats, sizeof(ciStats));
    std::memcpy(sim->Raw() + 0x4D0000, ciBlock, sizeof(ciBlock));

    const uintptr_t ciLcAddr = ciBase + 8;
    CHECK(session.IsValidLifeComponent(ciLcAddr, false),
          "session.IsValidLifeComponent phai chap nhan block CI co maxHP=1, hp=1, ECS prefix {2, 4}, ES cur/max hop le");
    CHECK(session.IsValidLifeComponent(ciLcAddr, true),
          "session.IsValidLifeComponent requireFullHP phai chap nhan khi hp == 1");

    // 2. Kiểm tra session.IsValidLifeComponent từ chối block 1, 1 ngẫu nhiên không có ECS signature và không có ES/Mana
    const uintptr_t randomBase = sim->BaseAddress() + 0x4D1000;
    uint8_t randomBlock[128] = {};
    const uint32_t randomStats[11] = {
        1, 1,           // +0, +4 HP/MaxHP = 1, 1 ngẫu nhiên
        0, 0, 0, 0,
        0, 0, 0, 0, 0
    };
    std::memcpy(randomBlock + 8, randomStats, sizeof(randomStats));
    std::memcpy(sim->Raw() + 0x4D1000, randomBlock, sizeof(randomBlock));
    const uintptr_t randomAddr = randomBase + 8;
    CHECK(!session.IsValidLifeComponent(randomAddr, false),
          "session.IsValidLifeComponent phai tu choi block 1, 1 ngau nhien khong co ECS signature va khong co ES/Mana");

    // 3. Kiểm tra ReflexManager với CI build (maxHP = 1, currentHP = 1, maxES = 5000)
    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);

    ReflexConfig cfg;
    cfg.burstDamageRatio = 0.22f;
    cfg.dodgeCooldownMs = 800;
    cfg.hpThreshold = 0.55f;
    cfg.criticalHpThreshold = 0.28f;
    cfg.lifeCooldownMs = 800;
    cfg.manaThreshold = 0.25f;
    cfg.manaCooldownMs = 1000;
    cfg.emergencyLogoutEnabled = true;
    cfg.emergencyHpThreshold = 0.35f;
    cfg.humanReactionEnabled = false; // Zero reaction latency để phản xạ tức thì trong test
    cfg.minReactionLatencyMs = 0;

    ReflexManager rm(cfg);
    TelemetryPacket pkt{};
    pkt.player.maxHP = 1;
    pkt.player.currentHP = 1;
    pkt.player.maxES = 5000;
    pkt.player.currentES = 5000;
    pkt.player.maxMana = 500;
    pkt.player.currentMana = 500;

    // Tick 1: Khởi tạo tại 1000ms
    rm.Update(pkt, kmbox, 1000);
    CHECK(rm.State().previousES == 5000, "previousES phai duoc khoi tao = 5000");
    CHECK(rm.State().dodgeCount == 0, "Dodge count ban dau = 0");
    CHECK(rm.State().lifeUses == 0, "Life uses ban dau = 0");
    CHECK(rm.State().emergencyTriggered == false, "Emergency chua duoc kich hoat");

    // Khi nhận burst damage vào ES (-1500 ES = 30% maxES), HandleIframeDodge kích hoạt né Iframe
    pkt.player.currentES = 3500; // 5000 - 1500 = 3500 (tụt 30% maxES >= 22% burstRatio)
    rm.Update(pkt, kmbox, 1050);
    CHECK(rm.State().dodgeCount == 1, "HandleIframeDodge phai kich hoat ne Iframe khi burst damage vao ES (-1500 ES = 30% maxES)");
    CHECK(rm.State().lifeUses == 0, "m_state.lifeUses phai van bang 0 khi burst ES");
    CHECK(rm.State().emergencyTriggered == false, "Emergency chua kich hoat vi ES van con 70% > 35%");

    // HandleLifeFlask không bao giờ tăng m_state.lifeUses cho CI
    rm.HandleLifeFlask(pkt.player, kmbox, 1100);
    CHECK(rm.State().lifeUses == 0, "HandleLifeFlask khong bao gio tang m_state.lifeUses cho CI");

    // Khi ES tụt sâu (1000 / 5000 = 20% <= 35%), HandleEmergencyLogout kích hoạt mở Portal 'T' mà m_state.lifeUses vẫn bằng 0
    pkt.player.currentES = 1000; // 20% <= 35% emergency threshold
    rm.Update(pkt, kmbox, 2000); // 2000ms >= 1050 + 800ms cooldown
    CHECK(rm.State().emergencyTriggered == true, "HandleEmergencyLogout phai kich hoat khi ES tut sau xuong 20% <= 35%");
    CHECK(rm.State().lifeUses == 0, "HandleEmergencyLogout khong duoc bam Life Flask cho CI (lifeUses van bang 0)");
    CHECK(rm.State().dodgeCount == 2, "HandleEmergencyLogout phai thuc hien emergency dodge roll");

    std::cout << "  -> Chaos Inoculation (CI) Invariants & Reflexes OK" << std::endl;
}

// ==========================================================
// 42. Realtime Character State, movementFlags & Log Sensors
// ==========================================================
void TestRealtimeCharacterStateAndLogSensors() {
    std::cout << "[Test 42] Realtime Character State & Log Sensors..." << std::endl;

    {
        GameSession session;
        std::string status;
        CHECK(session.Initialize("sim", status), "GameSession Init sim thanh cong");
        auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
        sim->SetAutoSimulate(false);

        TelemetryPacket pkt{};
        sim->Player()->currentHP = 0;
        session.Update(pkt);
        bool deadOk = (pkt.player.movementFlags == 8);

        sim->Player()->currentHP = 500;
        sim->Player()->posX = 0.0f;
        sim->Player()->posY = 0.0f;
        session.SetInGracePeriod(true);
        session.Update(pkt);
        bool graceOk = (pkt.player.movementFlags == 16);

        sim->Player()->posX = 100.0f;
        sim->Player()->posY = 100.0f;
        session.Update(pkt);
        sim->Player()->posX = 105.0f;
        sim->Player()->posY = 100.0f;
        session.Update(pkt);
        bool moveOk = (pkt.player.movementFlags == 1 && !session.InGracePeriod());

        session.Update(pkt);
        bool idleOk = (pkt.player.movementFlags == 4);

        session.NotifyRoll(0);
        session.Update(pkt);
        bool rollIdleOk = ((pkt.player.movementFlags & 2) != 0 && pkt.player.movementFlags == 6);

        sim->Player()->posX = 110.0f;
        session.Update(pkt);
        bool rollMoveOk = (pkt.player.movementFlags == 3);

        session.SetLastRollTime(1);
        session.Update(pkt);
        bool rollExpiredOk = ((pkt.player.movementFlags & 2) == 0 && pkt.player.movementFlags == 4);

        session.OnZoneLoadingFinished("Riverbank_Map");
        session.Update(pkt);
        bool reloadGraceOk = (session.InGracePeriod() && pkt.player.movementFlags == 16);

        CHECK(deadOk && graceOk && moveOk && idleOk && rollIdleOk && rollMoveOk && rollExpiredOk && reloadGraceOk,
              "Character movementFlags state machine transitions failed");
    }

    {
        LogSensor safeSensor;
        const std::string path = TestName("test_monsters_remain") + ".txt";
        LogSensor sensor(path);
        CHECK(sensor.Start(), "Start sensor MonstersRemain thất bại");

        {
            std::ofstream f(path, std::ios::binary);
        }
        auto waitDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
        while (!sensor.IsFileOpen() && std::chrono::steady_clock::now() < waitDeadline) {
            std::this_thread::sleep_for(std::chrono::milliseconds(25));
        }

        {
            std::ofstream f(path, std::ios::binary | std::ios::app);
            f << "2026/09/09 12:00:00 10001 [INFO Client 1] : More than 50 monsters remain.\n";
            f << "2026/09/09 12:00:01 10002 [INFO Client 1] : 14 monsters remain.\n";
            f << "2026/09/09 12:00:02 10003 [INFO Client 1] : There are 5 monsters remain.\n";
            f << "2026/09/09 12:00:03 10004 [INFO Client 1] : 1 monster remains.\n";
            f.flush();
        }

        std::vector<GameEvent> events;
        auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
        while (events.size() < 4 && std::chrono::steady_clock::now() < deadline) {
            GameEvent ev{};
            if (sensor.PollEvent(ev)) {
                events.push_back(ev);
            } else {
                std::this_thread::sleep_for(std::chrono::milliseconds(25));
            }
        }

        sensor.Stop();
        std::remove(path.c_str());

        bool monstersOk = (events.size() == 4) &&
                          (events[0].type == GameEventType::MonstersRemain && events[0].areaLevel == 50) &&
                          (events[1].type == GameEventType::MonstersRemain && events[1].areaLevel == 14) &&
                          (events[2].type == GameEventType::MonstersRemain && events[2].areaLevel == 5) &&
                          (events[3].type == GameEventType::MonstersRemain && events[3].areaLevel == 1);
        CHECK(monstersOk, "Monsters remain regex parsing failed");
    }

    std::cout << "  -> Realtime Character State & Log Sensors OK" << std::endl;
}

// ==========================================================
// 43. Authentic PID 36104 CI HP=1, Energy Shield, Ward & XYZ Autolock (Rule 8, 11 & 14)
// ==========================================================
void TestAuthenticPid36104CiEsWardAndXYZAutolock() {
    std::cout << "[Test 43] Authentic PID 36104 CI HP=1, ES, Ward & XYZ Autolock..." << std::endl;

    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "GameSession Init sim thanh cong");
    auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
    CHECK(sim != nullptr, "sim reader phai khac null");
    sim->SetAutoSimulate(false);

    // 1. Tái hiện chuẩn xác 100% cấu trúc memory dump thực tế từ PID 36104 PathOfExile.exe:
    //    LifeComponent tại 0x5331D2003E8 (mô phỏng tại base + 0x4E0000)
    //    [-8] = 2402, [-4] = 0 (KHÔNG CÓ CHỮ KÝ ECS {2, 4})
    //    +0 = 1 (Current HP), +4 = 1 (Max HP) [CI Keystone]
    //    +8 = 1 (Unreserved HP)
    //    +12 = 2402 (Current ES), +16 = 2402 (Max ES)
    //    +24 = 0, +28 = 0 (KHÔNG CÓ Flat Mana inline)
    //    +32 = 826 (Current Ward), +36 = 826 (Max Ward)
    const uintptr_t realCiBase = sim->BaseAddress() + 0x4E0000;
    uint8_t ciMem[128] = {};
    const uint32_t fakeHeader[2] = { 2402, 0 }; // KHÔNG CÓ chữ ký {2, 4}
    std::memcpy(ciMem, fakeHeader, sizeof(fakeHeader));

    const uint32_t realStats[10] = {
        1, 1,       // +0 HP, +4 Max HP
        1,          // +8 Unreserved HP
        2402, 2402, // +12 Current ES, +16 Max ES
        0,          // +20
        0, 0,       // +24, +28 (Không có Flat Mana)
        826, 826    // +32 Current Ward, +36 Max Ward
    };
    std::memcpy(ciMem + 8, realStats, sizeof(realStats));
    std::memcpy(sim->Raw() + 0x4E0000, ciMem, sizeof(ciMem));

    const uintptr_t realCiAddr = realCiBase + 8;

    // 2. Kiểm chứng IsValidLifeComponent chấp nhận struct CI thực tế có ES=2402/2402
    CHECK(session.IsValidLifeComponent(realCiAddr, false),
          "session.IsValidLifeComponent phai chap nhan struct CI thuc te (HP=1/1, ES=2402/2402) du khong co chu ky {2, 4}");

    // 3. Kiểm chứng PlayerFinder::AutoScanHP tìm ra và khóa chính xác realCiAddr
    PlayerFinder pf(*sim);
    uintptr_t foundCi = pf.AutoScanHP(realCiAddr, 64 * 1024, 0); // targetMaxHP = 0 (chế độ auto)
    CHECK(foundCi == realCiAddr, "AutoScanHP (targetMaxHP=0) phai tim duoc realCiAddr nho ES=2402/2402");

    uintptr_t foundCiExplicit = pf.AutoScanHP(realCiAddr, 64 * 1024, 1); // targetMaxHP = 1 (--ci flag)
    CHECK(foundCiExplicit == realCiAddr, "AutoScanHP (targetMaxHP=1) phai khoa chinh xac realCiAddr");

    // 4. Kiểm chứng AutoDetectShield tự động nhận diện và khóa ES tại +12 (+0x0C)
    session.SetPlayerAddress(realCiAddr);
    CHECK(session.AutoDetectShield(2402), "AutoDetectShield phai tim duoc ES 2402 tai offset +12 cua realCiAddr");
    CHECK(session.ShieldAddress() == realCiAddr + 12, "ShieldAddress phai khoa chinh xac realCiAddr + 12");

    // 5. Kiểm chứng AutoDetectWard tự động nhận diện và khóa Ward tại +32 (+0x20)
    CHECK(session.AutoDetectWard(826), "AutoDetectWard phai tim duoc Ward 826 tai offset +32 cua realCiAddr");
    CHECK(session.WardAddress() == realCiAddr + 32, "WardAddress phai khoa chinh xac realCiAddr + 32");

    // 6. Kiểm chứng StartStartupXYZCalibration có thể chạy độc lập khi m_playerAddr == 0
    GameSession sessXyz;
    sessXyz.Initialize("sim", status);
    sessXyz.SetPlayerAddress(0); // Giả lập HP chưa khóa
    // Chuyển sang backend RPM ảo hoặc reader để test StartStartupXYZCalibration không bị chặn bởi playerAddr == 0
    // (StartStartupXYZCalibration guard trên !m_reader || m_simulated, kiểm tra hàm IsValidXYZ với tọa độ hợp lệ gần gốc và Z=0)
    CHECK(PlayerFinder::IsValidXYZ(15.0f, 12.0f, 0.0f), "Toa do hop le gan goc POE2 Z=0 tren dia hinh phang phai PASS");
    CHECK(PlayerFinder::IsValidXYZ(2500.0f, 4000.0f, 0.0f), "Toa do Sandswept Marsh Z=0.0f phai PASS");

    std::cout << "  -> Authentic PID 36104 CI HP=1, ES, Ward & XYZ Autolock OK" << std::endl;
}

// ==========================================================
// 44. CI Vitals High ES, Ward & Camera Heuristics (Rule 8, 11 & 14)
// ==========================================================
void TestCIVitalsHighESAndWardRejection() {
    std::cout << "[Test 44] CI Vitals High ES, Ward Rejection & Camera Heuristics..." << std::endl;

    CHECK(vitals_fingerprint::IsInvertedEsPair(388, 54) && vitals_fingerprint::IsPlausibleEsPair(2416, 2416),
          "vitals_fingerprint ES pair heuristics failed");

    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "GameSession Init sim thanh cong");
    auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
    sim->SetAutoSimulate(false);

    const uintptr_t garbageBase = sim->BaseAddress() + 0x4F0000;
    uint8_t garbageMem[128] = {};
    const uint32_t fakeHeader[2] = { 2402, 0 };
    std::memcpy(garbageMem, fakeHeader, sizeof(fakeHeader));

    const uint32_t garbageStats[10] = { 1, 1, 1, 8, 46, 0, 0, 0, 0, 0 };
    std::memcpy(garbageMem + 8, garbageStats, sizeof(garbageStats));
    std::memcpy(sim->Raw() + 0x4F0000, garbageMem, sizeof(garbageMem));

    const uintptr_t garbageAddr = garbageBase + 8;
    PlayerFinder pf(*sim);
    pf.SetExpectedMaxES(2843);

    bool garbageRejected = !session.IsValidLifeComponent(garbageAddr, false) &&
                           !session.IsValidLifeComponent(garbageAddr, true) &&
                           (pf.AutoScanHP(garbageAddr, 2048, 1) == 0) &&
                           (pf.AutoScanHP(garbageAddr, 2048, 0) == 0);
    CHECK(garbageRejected, "Garbage CI entity with low ES must be rejected");

    const uintptr_t realCiBase = sim->BaseAddress() + 0x500000;
    uint8_t realCiMem[128] = {};
    const uint32_t ecsHeader[2] = { 2, 4 };
    std::memcpy(realCiMem, ecsHeader, sizeof(ecsHeader));

    const uint32_t realCiStats[10] = { 1, 1, 1, 2843, 2843, 0, 824, 824, 826, 826 };
    std::memcpy(realCiMem + 8, realCiStats, sizeof(realCiStats));
    std::memcpy(sim->Raw() + 0x500000, realCiMem, sizeof(realCiMem));

    const uintptr_t realCiAddr = realCiBase + 8;
    pf.SetExpectedMaxES(2843);
    uintptr_t scanResult = pf.AutoScanHP(sim->BaseAddress() + 0x4F8000, 256 * 1024, 1);
    uintptr_t scanResultAuto = pf.AutoScanHP(sim->BaseAddress() + 0x4F8000, 256 * 1024, 0);

    bool realCiAccepted = session.IsValidLifeComponent(realCiAddr, false) &&
                          session.IsValidLifeComponent(realCiAddr, true) &&
                          (scanResult == realCiAddr) && (scanResultAuto == realCiAddr);
    CHECK(realCiAccepted, "Real CI LifeComponent must be accepted and locked");

    session.SetPlayerAddress(realCiAddr);
    session.SetExpectedMaxHP(1);
    session.SetExpectedMaxES(2843);
    session.SetExpectedMaxWard(826);
    session.SetExpectedMaxMana(824);

    bool autoPointersOk = session.AutoDetectShield(2843) && (session.ShieldAddress() == realCiAddr + 12) &&
                          session.AutoDetectWard(826) && (session.WardAddress() == realCiAddr + 32) &&
                          session.AutoDetectManaPool(824) && (session.ManaAddress() == realCiAddr + 24);
    CHECK(autoPointersOk, "Auto-detect shield, ward, and mana pointers failed");

    std::cout << "  -> CI Vitals High ES & Ward Rejection OK" << std::endl;
}

// ==========================================================
// 45. PointerChainResolver - Kiểm tra bộ giải mã chuỗi con trỏ
// ==========================================================
void TestPointerChainResolver() {
    std::cout << "[Test 45] PointerChainResolver: InGameState, Player, EntityMap & O(1) Hot Path..." << std::endl;

    // 1. Kiểm tra hằng số & static roots
    const uintptr_t expectedRoots[8] = { 0x45CCFE0, 0x45CCFD8, 0x45D4100, 0x45D3DD8, 0x45D40F8, 0x4716880, 0x4435F90, 0x4435F98 };
    bool rootsOk = (PointerChainResolver::kKnownStaticRoots.size() == 8);
    for (size_t i = 0; i < 8 && rootsOk; ++i) {
        if (PointerChainResolver::kKnownStaticRoots[i] != expectedRoots[i]) rootsOk = false;
    }
    CHECK(rootsOk, "Static roots cua PointerChainResolver phai dung 8 dia chi RVA");

    // 2. AOB Pattern InGameStateBase
    const auto aob = AobPattern::Parse(PointerChainResolver::kInGameStatePattern);
    CHECK(aob.Valid() && aob.Size() == 22, "AOB Pattern InGameStateBase phai hop le (22 bytes)");

    // 3. Bounds Checking & Canonical Addresses
    bool addrChecksOk = !PointerChainResolver::IsValidAddress(0)
                     && !PointerChainResolver::IsValidAddress(0xFFF)
                     && !PointerChainResolver::IsValidAddress(0x9999)
                     && PointerChainResolver::IsValidAddress(0x10000)
                     && PointerChainResolver::IsValidAddress(0x140000000ULL)
                     && PointerChainResolver::IsValidAddress(0x7FFFFFFFFFFFULL)
                     && !PointerChainResolver::IsValidAddress(0x800000000000ULL);
    CHECK(addrChecksOk, "PointerChainResolver canonical address validation phai dung");

    // 4. Khởi tạo Simulated Backend
    SimulatedMemoryReader sim;
    CHECK(sim.Initialize() && sim.Attach(game_layout::kSimulatedPid), "Khoi tao va Attach SimulatedMemoryReader");

    PointerChainConfig cfg{};
    cfg.isSimulated = true;
    PointerChainResolver resolver(sim, cfg);
    CHECK(resolver.IsSimulated() && resolver.MainBase() == game_layout::kSimulatedBaseAddress,
          "Resolver phai nhan dien dung Simulated mode va MainBase");

    // 5. Giải mã InGameState
    uintptr_t inGame = 0;
    CHECK(resolver.ResolveInGameState(inGame) &&
          inGame == game_layout::kSimulatedBaseAddress + game_layout::kInGameStateOffset &&
          resolver.InGameAddress() == inGame &&
          resolver.ResolvedRva() == game_layout::kInGameStatePtrStaticOffset,
          "ResolveInGameState phai thanh cong va khop RVA 0x300000");

    // 6. Giải mã PlayerState
    ResolvedPlayerState player{};
    CHECK(resolver.ResolvePlayer(inGame, player), "ResolvePlayer phai thanh cong");
    bool playerFieldsOk = (player.posAddr == game_layout::kSimulatedBaseAddress + game_layout::kPlayerStateOffset)
                       && (player.lifeAddr == game_layout::kSimulatedBaseAddress + game_layout::kPlayerStateOffset + 16)
                       && (player.curHP == 1250 && player.maxHP == 1250)
                       && (player.curMana == 300 && player.maxMana == 300)
                       && (player.curSpirit == 100 && player.maxSpirit == 100)
                       && (player.curES == 0 && player.maxES == 0)
                       && (player.curWard == 0 && player.maxWard == 0)
                       && (player.x == 0.0f && player.y == 0.0f && player.z == 0.0f);
    CHECK(playerFieldsOk, "PlayerState resolved fields phai hop le");

    ResolvedPlayerState p2{};
    CHECK(resolver.ResolvePlayer(p2) && p2.curHP == 1250 && p2.curMana == 300,
          "ResolvePlayer overload khong can inGame phai khop gia tri");

    // 7. Giải mã EntityMap
    uintptr_t entityMap = 0;
    uint32_t cap = 0, count = 0;
    CHECK(resolver.ResolveEntityMap(inGame, entityMap, cap, count) &&
          entityMap == game_layout::kSimulatedBaseAddress + game_layout::kEntityMapOffset &&
          cap == game_layout::kMaxEntitySlots && count == 0,
          "ResolveEntityMap phai thanh cong voi cap=512, count=0");

    uintptr_t em2 = 0;
    uint32_t cap2 = 0, cnt2 = 0;
    CHECK(resolver.ResolveEntityMap(em2, cap2, cnt2) && em2 == entityMap && cap2 == cap && cnt2 == count,
          "ResolveEntityMap overload khong can inGame phai khop gia tri");

    // 8. InvalidateCache & Tái phục hồi
    resolver.InvalidateCache();
    CHECK(resolver.InGameAddress() == 0, "InvalidateCache phai dua InGameAddress ve 0");
    uintptr_t reResolved = 0;
    CHECK(resolver.ResolveInGameState(reResolved) && reResolved == inGame,
          "Resolve lai sau InvalidateCache phai thanh cong va khop dia chi");

    // 9. Phòng thủ với địa chỉ không hợp lệ
    ResolvedPlayerState dummyPlayer{};
    uintptr_t dummyMap = 0;
    uint32_t dummyCap = 0, dummyCnt = 0;
    bool invalidDefOk = !resolver.ResolvePlayer(0, dummyPlayer)
                     && !resolver.ResolvePlayer(0x100, dummyPlayer)
                     && !resolver.ResolvePlayer(0xFFFFFFFFFFFFFFFFULL, dummyPlayer)
                     && !resolver.ResolveEntityMap(0, dummyMap, dummyCap, dummyCnt)
                     && !resolver.ResolveEntityMap(0x500, dummyMap, dummyCap, dummyCnt);
    CHECK(invalidDefOk, "PointerChainResolver phai tu choi an toan cac dia chi invalid");

    // 10. Đo kiểm độ trễ Hot-Path O(1) (<0.001ms)
    constexpr int kIterations = 20000;
    const auto start = std::chrono::high_resolution_clock::now();
    ResolvedPlayerState benchPlayer{};
    for (int i = 0; i < kIterations; ++i) {
        resolver.ResolvePlayer(benchPlayer);
    }
    const auto end = std::chrono::high_resolution_clock::now();
    const double elapsedUs = std::chrono::duration<double, std::micro>(end - start).count();
    const double usPerCall = elapsedUs / kIterations;

    CHECK(usPerCall < 1.0, "ResolvePlayer O(1) phai nho hon 1.0 microsecond/call");
    std::cout << "  [Benchmark] Hot-Path ResolvePlayer: " << usPerCall << " us/call ("
              << usPerCall / 1000.0 << " ms) across " << kIterations << " iterations" << std::endl;
    std::cout << "  -> PointerChainResolver: InGameState, Player, EntityMap & O(1) Hot Path OK" << std::endl;
}

// ==========================================================
// SHA-256 self-contained + PE Fingerprint (TASK-RE-PIPELINE-2026)
//  - Kiểm chứng SHA-256 bằng test vector chuẩn NIST FIPS 180-4.
//  - Kiểm chứng bộ parse PE header/bảng section trên client THẬT.
// ==========================================================
void TestSha256NistVectorsAndPeFingerprint() {
    std::cout << "[Test 60] SHA-256 NIST vectors & PE Fingerprint..." << std::endl;

    const std::string abc = "abc";
    const std::string long448 = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
    bool nistVectorsOk = (pe_fingerprint::ToHex(pe_fingerprint::Sha256(reinterpret_cast<const uint8_t*>(abc.data()), abc.size())) ==
                          "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad") &&
                         (pe_fingerprint::ToHex(pe_fingerprint::Sha256(nullptr, 0)) ==
                          "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") &&
                         (pe_fingerprint::ToHex(pe_fingerprint::Sha256(reinterpret_cast<const uint8_t*>(long448.data()), long448.size())) ==
                          "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1");
    CHECK(nistVectorsOk, "SHA-256 NIST test vectors failed");

    std::vector<uint8_t> million(1000000, static_cast<uint8_t>('a'));
    pe_fingerprint::Sha256Context ctx;
    size_t offset = 0;
    while (offset < million.size()) {
        const size_t take = (std::min)(static_cast<size_t>(4097), million.size() - offset);
        ctx.Update(million.data() + offset, take);
        offset += take;
    }
    CHECK(pe_fingerprint::ToHex(ctx.Final()) ==
          "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0",
          "SHA-256 1,000,000 'a' odd-chunk streaming failed");

    pe_fingerprint::ClientBuildId build;
    std::string error;
    CHECK(!pe_fingerprint::LoadPeHeaders("Z:\\khong_ton_tai\\fake.exe", build, error) && !build.valid && !error.empty(),
          "LoadPeHeaders missing file safe fail check failed");

    std::string exePath;
    if (pe_fingerprint::LocatePoe2Executable(exePath)) {
        pe_fingerprint::ClientBuildId realBuild;
        std::string realError;
        bool peLoaded = pe_fingerprint::LoadPeHeaders(exePath, realBuild, realError) &&
                        realBuild.valid && realBuild.machine == IMAGE_FILE_MACHINE_AMD64 &&
                        realBuild.numberOfSections >= 5 && realBuild.sizeOfImage > 0 && realBuild.fileSize > 0;
        CHECK(peLoaded, "Real PathOfExile.exe PE header validation failed");

        const auto* text = realBuild.FindSection(".text");
        const auto* rdata = realBuild.FindSection(".rdata");
        const auto* data = realBuild.FindSection(".data");
        bool sectionsOk = text && rdata && data && text->Executable() && !text->Writable() &&
                          text->virtualSize > 32u * 1024 * 1024 && data->Writable() &&
                          realBuild.FindSection(".khongton") == nullptr;
        CHECK(sectionsOk, "Real PathOfExile.exe sections characteristics failed");

        std::cout << "  -> [AUTHENTIC] PathOfExile.exe fingerprint:" << std::endl;
        std::cout << "     Path          : " << exePath << std::endl;
        std::cout << "     BuildId       : " << realBuild.ShortId() << std::endl;
        std::cout << "     TimeDateStamp : 0x" << std::hex << realBuild.timeDateStamp << std::dec << std::endl;
        std::cout << "     SizeOfImage   : 0x" << std::hex << realBuild.sizeOfImage << std::dec << std::endl;
        std::cout << "     FileSize      : " << realBuild.fileSize << " bytes" << std::endl;
        std::cout << "     LayoutHash    : 0x" << std::hex << realBuild.SectionLayoutHash() << std::dec << std::endl;

        pe_fingerprint::ClientBuildId again;
        std::string err2;
        CHECK(pe_fingerprint::LoadPeHeaders(exePath, again, err2) && realBuild.SameBuildAs(again) &&
              realBuild.SectionLayoutHash() == again.SectionLayoutHash(),
              "Real PE fingerprint stability check failed");
    }

    std::cout << "  -> SHA-256 NIST + PE Fingerprint OK" << std::endl;
}

// ==========================================================
// INV-BUILD-01 Regression: .data section dịch -0x1000 giữa 2 build client
//  DỮ LIỆU THẬT (Rule 14) trích từ bin/Release/memprobe_report.txt:
//    BUILD CŨ (xuất hiện 5 phiên): .text VSize=0x2E840BE, .rdata VA=0x2E86000
//                                  VSize=0xB7C3A6, .data VA=0x3A03000 VSize=0xD163DC
//                                  module size = 0x4C9D000
//    BUILD MỚI (xuất hiện 1 phiên; khớp PE header của exe đang cài 10/09/2026):
//                                  .text VSize=0x2E83B6E, .rdata VA=0x2E85000
//                                  VSize=0xB7C366, .data VA=0x3A02000 VSize=0xD1655C
//                                  TimeDateStamp=0x6A9E477A, SizeOfImage=0x4C9C000
//  TimeDateStamp của BUILD CŨ KHÔNG được MemProbe ghi nhận (chính đây là một phần
//  root cause) -> để 0, KHÔNG bịa giá trị; chỉ phân biệt bằng SectionLayoutHash.
// ==========================================================
void TestClientBuildShiftAndOffsetRegistryGate() {
    std::cout << "[Test 61] INV-BUILD-01 Client Build Shift & OffsetRegistry Gate..." << std::endl;

    constexpr uint32_t kCharCode = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE;
    constexpr uint32_t kCharReadOnly = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA;
    constexpr uint32_t kCharReadWrite = kCharReadOnly | IMAGE_SCN_MEM_WRITE;

    auto MakeSection = [](const char* name, uint32_t va, uint32_t vsize, uint32_t chars) {
        pe_fingerprint::SectionInfo info;
        info.name = name;
        info.virtualAddress = va;
        info.virtualSize = vsize;
        info.rawSize = vsize;
        info.characteristics = chars;
        return info;
    };

    pe_fingerprint::ClientBuildId newBuild;
    newBuild.valid = true;
    newBuild.machine = IMAGE_FILE_MACHINE_AMD64;
    newBuild.numberOfSections = 7;
    newBuild.timeDateStamp = 0x6A9E477Au;
    newBuild.sizeOfImage = 0x4C9C000u;
    newBuild.sizeOfHeaders = 0x400u;
    newBuild.fileSize = 78319448ull;
    newBuild.sections = {
        MakeSection(".text",  0x00001000u, 0x02E83B6Eu, kCharCode),
        MakeSection(".rdata", 0x02E85000u, 0x00B7C366u, kCharReadOnly),
        MakeSection(".data",  0x03A02000u, 0x00D1655Cu, kCharReadWrite),
        MakeSection(".pdata", 0x04719000u, 0x001C4AE4u, kCharReadOnly),
        MakeSection("_RDATA", 0x048DE000u, 0x00004E10u, kCharReadOnly),
        MakeSection(".rsrc",  0x048E3000u, 0x0027FDF8u, kCharReadOnly),
        MakeSection(".reloc", 0x04B63000u, 0x00138AF8u, kCharReadOnly)
    };

    pe_fingerprint::ClientBuildId oldBuild;
    oldBuild.valid = true;
    oldBuild.machine = IMAGE_FILE_MACHINE_AMD64;
    oldBuild.numberOfSections = 3;
    oldBuild.timeDateStamp = 0;
    oldBuild.sizeOfImage = 0x4C9D000u;
    oldBuild.sections = {
        MakeSection(".text",  0x00001000u, 0x02E840BEu, kCharCode),
        MakeSection(".rdata", 0x02E86000u, 0x00B7C3A6u, kCharReadOnly),
        MakeSection(".data",  0x03A03000u, 0x00D163DCu, kCharReadWrite)
    };

    // 1. Nhận diện khác biệt giữa 2 build
    CHECK(!oldBuild.SameBuildAs(newBuild) && oldBuild.SectionLayoutHash() != newBuild.SectionLayoutHash() &&
          newBuild.SameBuildAs(newBuild) && newBuild.ShortId() == "6A9E477A" &&
          !pe_fingerprint::ClientBuildId{}.SameBuildAs(newBuild),
          "INV-BUILD-01: build cu va build moi PHAI khac nhau (khac hash section layout)");

    // 2. OffsetRegistry khóa theo BUILD CŨ -> từ chối BUILD MỚI
    const std::string regPath = TestName("test_offset_registry_old_build.toml");
    DeleteFileA(regPath.c_str());
    {
        OffsetsStore s;
        s.Set("build_section_layout_hash", oldBuild.SectionLayoutHash());
        s.Set("static_root_1", 0x45CFEF8);
        s.Set("static_root_2", 0x4715740);
        s.SetString("aob_pattern_1", "48 8B 05 ? ? ? ? 48 8B 40 38 48 8B 88 ? ? ? ? 48 85 C9 74");
        s.Set("entity_map_offset", 0x3E8);
        s.Set("stat_offset", 0x3B8);
        s.Set("pos_offset", 0x518);
        CHECK(s.Save(regPath), "Save registry that bai");
    }
    OffsetRegistry oldRegistry;
    CHECK(oldRegistry.Load(regPath), "Load registry that bai");
    CHECK(oldRegistry.BuildTimeDateStamp() == 0 &&
          oldRegistry.CheckBuild(newBuild) == OffsetRegistry::BuildMatch::LayoutMismatch &&
          !oldRegistry.IsUsableFor(newBuild) &&
          oldRegistry.CheckBuild(oldBuild) == OffsetRegistry::BuildMatch::Match &&
          oldRegistry.IsUsableFor(oldBuild),
          "INV-BUILD-01: registry build cu bi tu choi tren build moi (LayoutMismatch)");

    // 3. Nội dung tri thức RE đọc đúng từ registry
    const auto roots = oldRegistry.StaticRootCandidates();
    const auto patterns = oldRegistry.AobPatterns();
    CHECK(roots.size() == 2 && roots[0] == 0x45CFEF8 && roots[1] == 0x4715740 &&
          patterns.size() == 1 && patterns[0] == "48 8B 05 ? ? ? ? 48 8B 40 38 48 8B 88 ? ? ? ? 48 85 C9 74" &&
          oldRegistry.EntityMapOffset() == 0x3E8 && oldRegistry.StatOffset() == 0x3B8 && oldRegistry.PosOffset() == 0x518,
          "Tri thuc offset static roots va AOB patterns doc chinh xac tu registry");

    // 4. Registry thiếu build fingerprint -> NoRecord -> không được dùng
    const std::string anonPath = TestName("test_offset_registry_no_build.toml");
    DeleteFileA(anonPath.c_str());
    {
        OffsetsStore s;
        s.Set("static_root_1", 0x45CFEF8);
        s.Save(anonPath);
    }
    OffsetRegistry anonRegistry;
    CHECK(anonRegistry.Load(anonPath) && anonRegistry.CheckBuild(newBuild) == OffsetRegistry::BuildMatch::NoRecord &&
          !anonRegistry.IsUsableFor(newBuild),
          "Registry thieu build fingerprint phai tra ve NoRecord va khong duoc phep dung");

    // 5. Không đọc được fingerprint của client -> LiveUnavailable
    pe_fingerprint::ClientBuildId invalidBuild;
    CHECK(oldRegistry.CheckBuild(invalidBuild) == OffsetRegistry::BuildMatch::LiveUnavailable &&
          std::string(OffsetRegistry::ToString(OffsetRegistry::BuildMatch::Match)) == "Match",
          "Client khong doc duoc fingerprint phai tra ve LiveUnavailable");

    // 6. Registry không tồn tại -> an toàn
    OffsetRegistry missing;
    CHECK(!missing.Load(TestName("khong_ton_tai_registry.toml")) && !missing.Loaded() &&
          missing.StaticRootCandidates().empty() && missing.AobPatterns().empty(),
          "Load registry khong ton tai phai that bai an toan");

    DeleteFileA(regPath.c_str());
    DeleteFileA(anonPath.c_str());

    // 7. PointerChainResolver build gate vô hiệu static roots khi lệch build
    SimulatedMemoryReader sim;
    CHECK(sim.Initialize() && sim.Attach(game_layout::kSimulatedPid), "Khoi tao sim");
    PointerChainConfig cfg;
    cfg.isSimulated = true;
    PointerChainResolver resolver(sim, cfg);
    bool initialAllowed = resolver.CompiledStaticRootsAllowed();
    resolver.SetBuildGate(true, false);
    bool disabledOnMismatch = resolver.BuildGateArmed() && !resolver.BuildGateMatches() && !resolver.CompiledStaticRootsAllowed();
    resolver.SetBuildGate(true, true);
    bool enabledOnMatch = resolver.BuildGateMatches() && resolver.CompiledStaticRootsAllowed();
    CHECK(initialAllowed && disabledOnMismatch && enabledOnMatch,
          "INV-BUILD-01: Build Gate khoa static roots khi lech build va mo khi khop");

    std::cout << "  -> INV-BUILD-01 Build Shift & OffsetRegistry Gate OK" << std::endl;
}

void TestRegistryLiveLabel() {
    std::cout << "[Test 62] Live label registry: ValidateInGameState gan InGameState, loai RVA rac..." << std::endl;

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize() && sim.Attach(game_layout::kSimulatedPid), "Khoi tao va Attach SimulatedMemoryReader");

    const std::string tomlPath = TestName("live_label_registry") + ".toml";
    {
        std::ofstream file(tomlPath);
        file << "build_time_date_stamp = 0x1\n";
        file << "build_section_layout_hash = 0x1\n";
        file << "static_root_1 = 0x" << std::hex << game_layout::kInGameStatePtrStaticOffset << "\n";
        file << "static_root_2 = 0x301000\n";
        file << std::dec;
    }

    OffsetRegistry registry;
    CHECK(registry.Load(tomlPath) && registry.StaticRootCandidates().size() == 2, "Nap TOML live-label tam");

    ModuleInfo mod;
    mod.name = "PathOfExile.exe";
    mod.base = game_layout::kSimulatedBaseAddress;
    mod.size = game_layout::kSimulatedMemorySize;

    registry_live_label::Report report;
    CHECK(registry_live_label::Probe(sim, registry, mod, true, report), "Probe simulated phai thanh cong");

    bool labeledInGame = false, labeledLocalPlayer = false, labeledEntityMap = false, hasHeapTarget = false;
    for (const auto& label : report.labels) {
        if (label.target == "InGameState" && label.rva == game_layout::kInGameStatePtrStaticOffset) labeledInGame = true;
        if (label.target == "LocalPlayer" && label.rva == game_layout::kPlayerStatePtrOffset) labeledLocalPlayer = true;
        if (label.target == "EntityMap" && label.rva == game_layout::kEntityMapRootOffset) labeledEntityMap = true;
        if (label.target == "player_addr") hasHeapTarget = true;
    }
    CHECK(labeledInGame && labeledLocalPlayer && labeledEntityMap && !hasHeapTarget,
          "Gắn nhãn chuẩn InGameState, LocalPlayer, EntityMap và loại bỏ target heap (INV-OFFSET-01)");

    std::remove(tomlPath.c_str());
    std::cout << "  -> Live label registry OK" << std::endl;
}

void TestVitalsFingerprintCiHud() {
    std::cout << "[Test 63] Vitals fingerprint CI HUD (ES=2416 Ward=826 Mana=828 Spirit=8/138)..." << std::endl;

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize(), "Khoi tao SimulatedMemoryReader");
    CHECK(sim.Attach(game_layout::kSimulatedPid), "Attach SimulatedMemoryReader");

    const uintptr_t life = game_layout::kSimulatedBaseAddress + 0x500000;
    const uint32_t ecs[2] = { 2, 4 };
    CHECK(sim.Write(life - 8, ecs, sizeof(ecs)), "Ghi ECS {2,4}");
    const uint32_t block[12] = {
        1, 1,           // +0/+4 CI HP
        1,              // +8 unreserved
        2416, 2416,     // +12/+16 ES (HUD 10/09/2026)
        0,
        828, 828,       // +24/+28 Mana
        826, 826,       // +32/+36 Ward
        8, 138          // +40/+44 Spirit 8/138
    };
    CHECK(sim.Write(life, block, sizeof(block)), "Ghi LifeComponent HUD");

    vitals_fingerprint::Expected exp;
    exp.maxHP = 1;
    exp.maxES = 2416;
    exp.maxWard = 826;
    exp.maxMana = 828;
    exp.maxSpirit = 138;
    exp.curSpirit = 8;

    const auto hits = vitals_fingerprint::ScanHeap(sim, exp, 8);
    CHECK(!hits.empty(), "ScanHeap phai tim Life CI khop HUD");
    CHECK(hits.front().lifeAddr == life, "lifeAddr phai dung o 0x500000");
    CHECK(hits.front().es.offset == 12 && hits.front().es.max == 2416, "ES +12/+16 = 2416");
    CHECK(hits.front().ward.offset == 32 && hits.front().ward.max == 826, "Ward +32/+36 = 826");
    CHECK(hits.front().mana.offset == 24 && hits.front().mana.max == 828, "Mana +24/+28 = 828");
    CHECK(hits.front().spirit.offset == 40 && hits.front().spirit.cur == 8 &&
          hits.front().spirit.max == 138, "Spirit +40 = 8/138");
    CHECK(hits.front().ecsSignature, "Phai co chu ky ECS {2,4}");

    const uintptr_t inGame = game_layout::kSimulatedBaseAddress + 0x510000;
    CHECK(sim.Write(inGame + 0x10, &life, sizeof(life)), "InGameState+0x10 -> life");
    vitals_fingerprint::LifeMatch viaChain;
    CHECK(vitals_fingerprint::FindLifeFromInGame(sim, inGame, exp, viaChain),
          "FindLifeFromInGame phai di +0x10 toi Life");
    CHECK(viaChain.lifeAddr == life, "Chain lifeAddr dung");

    exp.maxES = 9999;
    const auto miss = vitals_fingerprint::ScanHeap(sim, exp, 8);
    CHECK(miss.empty(), "Fingerprint ES sai khong duoc khop");
}

void TestVitalsFingerprintRegionEdge() {
    std::cout << "[Test 64] Vitals fingerprint Life sat dau region (life-64 NOACCESS)..." << std::endl;

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize(), "Khoi tao SimulatedMemoryReader");
    CHECK(sim.Attach(game_layout::kSimulatedPid), "Attach SimulatedMemoryReader");
    sim.SetAutoSimulate(false);

    // life-64 nam truoc kSimulatedBaseAddress -> Read cua so 1088-byte phai that bai.
    const uintptr_t life = sim.BaseAddress() + 8;
    const uint32_t block[10] = {
        1, 1,           // +0/+4 CI HP
        1,              // +8 unreserved
        2416, 2416,     // +12/+16 ES
        0,
        828, 828,       // +24/+28 Mana
        826, 826        // +32/+36 Ward
    };
    CHECK(sim.Write(life, block, sizeof(block)), "Ghi LifeComponent sat dau region");

    vitals_fingerprint::Expected exp;
    exp.maxHP = 1;
    exp.maxES = 2416;
    exp.maxWard = 826;
    exp.maxMana = 828;

    vitals_fingerprint::LifeMatch at;
    CHECK(vitals_fingerprint::MatchAt(sim, life, exp, at),
          "MatchAt khong duoc phu thuoc Read(life-64, 1088)");
    CHECK(at.lifeAddr == life, "lifeAddr sat dau region");
    CHECK(at.es.offset == 12 && at.es.max == 2416, "ES canonical +12");
    CHECK(at.ward.offset == 32 && at.ward.max == 826, "Ward canonical +32");

    vitals_fingerprint::ScanStats stats;
    const auto hits = vitals_fingerprint::ScanHeap(sim, exp, 8, &stats);
    CHECK(!hits.empty(), "ScanHeap tim Life sat dau region");
    bool foundLife = false;
    for (const auto& h : hits) {
        if (h.lifeAddr == life) {
            foundLife = true;
            break;
        }
    }
    if (!foundLife && !hits.empty()) {
        std::cout << "  [Test 64] front=0x" << std::hex << hits.front().lifeAddr
                  << " expect=0x" << life << std::dec
                  << " n=" << hits.size()
                  << " score=" << hits.front().score
                  << " " << vitals_fingerprint::Describe(hits.front()) << std::endl;
    }
    CHECK(foundLife, "ScanHeap lifeAddr dung");
    CHECK(stats.esDwords > 0, "Phai dem duoc dword 2416");
}

void TestAuthenticPid36104LifeDumpNoInlineMana() {
    std::cout << "[Test 65] Authentic PID 36104 dump 15:19 Life 1/1 ES=2416 Ward=826, Mana khong inline..." << std::endl;

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize(), "Khoi tao SimulatedMemoryReader");
    CHECK(sim.Attach(game_layout::kSimulatedPid), "Attach SimulatedMemoryReader");
    sim.SetAutoSimulate(false);

    // Hexdump that 0x5331D2003E8 (80 byte dau) — 10/09/2026 15:19:27 PID 36104
    const uint8_t dump[] = {
        0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
        0x01, 0x00, 0x00, 0x00, 0x70, 0x09, 0x00, 0x00,
        0x70, 0x09, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
        0x3A, 0x03, 0x00, 0x00, 0x3A, 0x03, 0x00, 0x00
    };
    const uintptr_t life = sim.BaseAddress() + 0x600000;
    CHECK(sim.Write(life, dump, sizeof(dump)), "Ghi dump Life PID 36104");

    vitals_fingerprint::Expected exp;
    exp.maxHP = 1;
    exp.maxES = 2416;
    exp.maxWard = 826;
    exp.maxMana = 828;
    exp.maxSpirit = 138;
    exp.curSpirit = 8;

    vitals_fingerprint::LifeMatch at;
    CHECK(vitals_fingerprint::MatchAt(sim, life, exp, at),
          "Thieu Mana inline khong duoc loai Life CI+ES+Ward");
    CHECK(at.es.offset == 12 && at.es.max == 2416, "ES +12 = 2416");
    CHECK(at.ward.offset == 32 && at.ward.max == 826, "Ward +32 = 826");
    CHECK(at.mana.offset < 0, "Mana khong duoc gan nha vao +24 (0xFFFFFFFF)");

    const auto hits = vitals_fingerprint::ScanHeap(sim, exp, 8);
    bool found = false;
    for (const auto& h : hits) {
        if (h.lifeAddr == life) {
            found = true;
            break;
        }
    }
    CHECK(found, "ScanHeap phai tim dump that 2416/826");
}

void TestScanCurMaxPairFindsDetachedManaSpirit() {
    std::cout << "[Test 66] ScanCurMaxPair tim Mana 828/828 va Spirit 8/138 tach khoi Life..." << std::endl;

    SimulatedMemoryReader sim;
    CHECK(sim.Initialize(), "Khoi tao SimulatedMemoryReader");
    CHECK(sim.Attach(game_layout::kSimulatedPid), "Attach SimulatedMemoryReader");
    sim.SetAutoSimulate(false);

    const uint8_t dump[] = {
        0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
        0x01, 0x00, 0x00, 0x00, 0x70, 0x09, 0x00, 0x00,
        0x70, 0x09, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
        0x3A, 0x03, 0x00, 0x00, 0x3A, 0x03, 0x00, 0x00
    };
    const uintptr_t life = sim.BaseAddress() + 0x600000;
    const uintptr_t mana = sim.BaseAddress() + 0x700000;
    const uintptr_t spirit = sim.BaseAddress() + 0x710000;
    CHECK(sim.Write(life, dump, sizeof(dump)), "Ghi dump Life PID 36104");
    const uint32_t manaPair[2] = { 828, 828 };
    const uint32_t spiritPair[2] = { 8, 138 };
    CHECK(sim.Write(mana, manaPair, sizeof(manaPair)), "Ghi Mana 828/828 HUD");
    CHECK(sim.Write(spirit, spiritPair, sizeof(spiritPair)), "Ghi Spirit 8/138 HUD");

    const uint32_t manaCanon[2] = { 828, 828 };
    const uint32_t spiritCanon[2] = { 8, 138 };
    CHECK(sim.Write(life + vitals_fingerprint::kManaRel, manaCanon, sizeof(manaCanon)),
          "Ghi Mana tai Life+0x420");
    CHECK(sim.Write(life + vitals_fingerprint::kSpiritRel, spiritCanon, sizeof(spiritCanon)),
          "Ghi Spirit tai Life+0x44C");

    vitals_fingerprint::Expected exp;
    exp.maxHP = 1;
    exp.maxES = 2416;
    exp.maxWard = 826;
    exp.maxMana = 828;
    exp.maxSpirit = 138;
    exp.curSpirit = 8;
    vitals_fingerprint::LifeMatch at;
    CHECK(vitals_fingerprint::MatchAt(sim, life, exp, at), "MatchAt Life+Mana+Spirit canonic");
    CHECK(at.mana.offset == vitals_fingerprint::kManaRel && at.mana.max == 828,
          "Mana relative +0x420");
    CHECK(at.spirit.offset == vitals_fingerprint::kSpiritRel && at.spirit.cur == 8 &&
          at.spirit.max == 138, "Spirit relative +0x44C");

    const auto manaHits = vitals_fingerprint::ScanCurMaxPair(sim, 828, 828, 8);
    bool foundMana = false;
    for (const auto& h : manaHits) {
        if (h.addr == mana && h.cur == 828 && h.max == 828) {
            foundMana = true;
            break;
        }
    }
    CHECK(foundMana, "ScanCurMaxPair phai tim Mana 828/828 tach Life");

    const auto spiritHits = vitals_fingerprint::ScanCurMaxPair(sim, 8, 138, 8);
    bool foundSpirit = false;
    for (const auto& h : spiritHits) {
        if (h.addr == spirit && h.cur == 8 && h.max == 138) {
            foundSpirit = true;
            break;
        }
    }
    CHECK(foundSpirit, "ScanCurMaxPair phai tim Spirit 8/138 tach Life");

    const auto miss = vitals_fingerprint::ScanCurMaxPair(sim, 8, 9999, 8);
    CHECK(miss.empty(), "Spirit max sai khong duoc khop");
}

// ==========================================================
// 68. Dynamic XYZ Autolock & Static Anchor Rejection (INV-MEM-DYNAMIC-XYZ / Rule 8 & 9)
// ==========================================================
void TestDynamicXyzAutolockAndStaticAnchorRejection() {
    std::cout << "[Test 68] Dynamic XYZ Autolock & Static Anchor Rejection (0x290/0x540/0x120)..." << std::endl;

    {
        SimulatedMemoryReader sim;
        CHECK(sim.Initialize() && sim.Attach(game_layout::kSimulatedPid), "sim attach failed");
        sim.SetAutoSimulate(false);

        PointerChainConfig cfg{};
        cfg.isSimulated = false;
        cfg.playerOffset = 0xBE0;
        cfg.statOffset = 0x3D8;
        PointerChainResolver resolver(sim, cfg);
        resolver.SetSimulated(false);

        const uintptr_t inGame = sim.BaseAddress() + 0x200000;
        const uintptr_t playerPtr = sim.BaseAddress() + 0x300000;
        sim.WriteValue<uintptr_t>(inGame + 0xBE0, playerPtr);

        uint32_t hp[2] = { 1000, 1000 };
        sim.Write(playerPtr + 0x3D8, hp, sizeof(hp));

        float hideoutSpawn[3] = { 219.44f, 99.0f, 0.0f };
        sim.Write(playerPtr + 0x290, hideoutSpawn, sizeof(hideoutSpawn));
        float creekSpawn[3] = { 29.0f, -135.0f, 0.0f };
        sim.Write(playerPtr + 0x540, creekSpawn, sizeof(creekSpawn));
        float anchor120[3] = { 16.0f, -135.0f, 0.0f };
        sim.Write(playerPtr + 0x120, anchor120, sizeof(anchor120));

        resolver.SetPosOffset(0x290);
        ResolvedPlayerState pState{};
        bool anchor290Ok = resolver.ResolvePlayer(inGame, pState) && (pState.posAddr == 0);

        resolver.SetPosOffset(0x540);
        pState = {};
        bool anchor540Ok = resolver.ResolvePlayer(inGame, pState) && (pState.posAddr == 0);

        resolver.SetPosOffset(0x120);
        pState = {};
        bool anchor120Ok = resolver.ResolvePlayer(inGame, pState) && (pState.posAddr == 0);

        CHECK(anchor290Ok && anchor540Ok && anchor120Ok, "Static anchors 0x290, 0x540, 0x120 rejection failed");

        float dynamicCoords[3] = { 4177.15f, 6517.61f, 2506.74f };
        sim.Write(playerPtr + 0x400, dynamicCoords, sizeof(dynamicCoords));
        resolver.SetPosOffset(0x400);
        pState = {};
        bool dynOk = resolver.ResolvePlayer(inGame, pState) && (pState.posAddr == playerPtr + 0x400) &&
                     (std::abs(pState.x - 4177.15f) < 0.01f);

        resolver.SetSimulated(true);
        pState = {};
        bool simOk = resolver.ResolvePlayer(pState) && (pState.posAddr != 0);
        CHECK(dynOk && simOk, "Dynamic offset 0x400 & Simulated mode resolve failed");
    }

    {
        GameSession session;
        std::string status;
        CHECK(session.Initialize("sim", status), "GameSession Init sim thanh cong");
        auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
        sim->SetAutoSimulate(false);

        const uintptr_t inGame = sim->BaseAddress() + 0x200000;
        const uintptr_t playerPtr = sim->BaseAddress() + 0x300000;
        sim->WriteValue<uintptr_t>(inGame + 0xBE0, playerPtr);

        uint32_t hp[2] = { 1000, 1000 };
        sim->Write(playerPtr + 0x3D8, hp, sizeof(hp));
        float hideoutSpawn[3] = { 219.44f, 99.0f, 0.0f };
        sim->Write(playerPtr + 0x290, hideoutSpawn, sizeof(hideoutSpawn));

        session.SetSimulated(false);
        session.SetInGameAddress(inGame);
        session.SetPlayerAddress(0);
        session.SetPositionAddress(0);
        if (session.PointerResolver()) {
            session.PointerResolver()->SetInGameAddress(inGame);
            session.PointerResolver()->SetPlayerOffset(0xBE0);
            session.PointerResolver()->SetStatOffset(0x3D8);
            session.PointerResolver()->SetPosOffset(0x290);
        }

        const bool resolved = session.ResolveViaPointerChain();
        CHECK(resolved && session.PositionAddress() == 0 &&
              session.AutoScanState() != GameSession::AutoPositionState::RESOLVED &&
              session.AutoScanState() == GameSession::AutoPositionState::IDLE,
              "ResolveViaPointerChain must not set RESOLVED when posAddr == 0");

        session.StartStartupXYZCalibration();
        CHECK(session.AutoScanState() == GameSession::AutoPositionState::CAPTURING_STILL_BASELINE,
              "StartStartupXYZCalibration failed");
    }

    std::cout << "  -> Dynamic XYZ Autolock & Static Anchor Rejection OK" << std::endl;
}

// ==========================================================
// 71. Ward vs Mana Disambiguation & Inline Ward Protection
// ==========================================================
void TestWardManaDisambiguationAndNoFalseLock() {
    std::cout << "[Test 71] Ward vs Mana Disambiguation & Inline Ward Protection..." << std::endl;

    GameSession session;
    std::string status;
    CHECK(session.Initialize("sim", status), "Khoi tao GameSession sim");
    auto* sim = static_cast<SimulatedMemoryReader*>(session.Reader());
    sim->SetAutoSimulate(false);

    // Chuẩn bị layout bộ nhớ CI: LifeComponent tại 0x650000
    // +0: 1/1 (HP), +12: 4284/4284 (ES), +20: -1, +24: 0, +28: -1
    // +32: 241/241 (Ward)
    // +88: Con trỏ tới ManaComponent (0x651000) chứa Mana 897/897
    const uintptr_t lifeAddr = sim->BaseAddress() + 0x650000;
    const uintptr_t manaCompAddr = sim->BaseAddress() + 0x651000;

    uint8_t lifeMem[256] = {};
    const uint32_t hp[2] = { 1, 1 };
    const uint32_t es[2] = { 4284, 4284 };
    const uint32_t ward[2] = { 241, 241 };
    const uint32_t noMana[2] = { 0, 0xFFFFFFFF };

    std::memcpy(lifeMem + 0, hp, sizeof(hp));
    std::memcpy(lifeMem + 12, es, sizeof(es));
    std::memcpy(lifeMem + 24, noMana, sizeof(noMana));
    std::memcpy(lifeMem + 32, ward, sizeof(ward));

    // Con trỏ tới ManaComponent tại offset +88
    uint64_t manaCompPtr64 = static_cast<uint64_t>(manaCompAddr);
    std::memcpy(lifeMem + 88, &manaCompPtr64, sizeof(uint64_t));

    // Dữ liệu ManaComponent tại manaCompAddr: 897/897
    uint32_t manaData[2] = { 897, 897 };
    sim->Write(manaCompAddr, manaData, sizeof(manaData));
    sim->Write(lifeAddr, lifeMem, sizeof(lifeMem));

    session.SetSimulated(false); // Cho phép Check 2 dereference pointer trên reader
    session.SetPlayerAddress(lifeAddr);
    session.SetExpectedMaxHP(1);
    session.SetExpectedMaxES(4284);
    session.SetExpectedMaxWard(241);

    // 1. Kiểm chứng AutoDetectWard khóa chính xác offset +32 (Ward 241/241)
    CHECK(session.AutoDetectWard(241), "AutoDetectWard phai tim va khoa Ward 241 tai lifeAddr + 32");
    CHECK(session.WardAddress() == lifeAddr + 32, "WardAddress phai o lifeAddr + 32");

    // 2. Kiểm chứng AutoDetectManaPool(0) (Zero-Input) TUYỆT ĐỐI KHÔNG khóa nhầm Ward (+32) làm Mana
    CHECK(session.AutoDetectManaPool(0), "AutoDetectManaPool(0) phai tim Mana tu Component Pointer");
    CHECK(session.ManaAddress() != lifeAddr + 32, "ManaAddress TUYET DOI KHONG DUOC la Ward (lifeAddr + 32)");
    CHECK(session.ManaAddress() == manaCompAddr, "ManaAddress phai la manaCompAddr (897/897)");

    // 3. Kiểm thử kịch bản ngược lại: Mana dò trước khi Ward được dò
    session.SetWardAddress(0);
    session.SetExpectedMaxWard(0);
    session.ResetManaAddress();
    CHECK(session.WardAddress() == 0, "Ban dau WardAddress == 0");
    CHECK(!session.ManaCalibrated(), "Ban dau Mana chua calibrated");

    // Chạy AutoDetectManaPool(0) TRƯỚC:
    // Phải bỏ qua +32/+36 và tìm thấy ManaComponent 897/897
    CHECK(session.AutoDetectManaPool(0), "AutoDetectManaPool(0) chay truoc Ward");
    CHECK(session.ManaAddress() != lifeAddr + 32, "ManaAddress khong duoc khoa +32 du wardAddr chua set");
    CHECK(session.ManaAddress() == manaCompAddr, "ManaAddress phai khoa manaCompAddr");

    // Sau đó chạy AutoDetectWard(0):
    CHECK(session.AutoDetectWard(0), "AutoDetectWard(0) phai khoa +32");
    CHECK(session.WardAddress() == lifeAddr + 32, "WardAddress phai khoa +32");
    CHECK(session.ManaAddress() == manaCompAddr, "ManaAddress van phai giu nguyen manaCompAddr");

    std::cout << "  -> Ward vs Mana Disambiguation & Inline Ward Protection OK" << std::endl;
}


