// ==========================================================
// Domain 4: Navigation & Spatial 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>

void TestTerrainGridAndLineOfSight() {
    std::cout << "[Test 20] TerrainGrid & Line of Sight (LoS)..." << std::endl;

    navigation::TerrainGrid grid(6.0f);
    grid.Initialize(100.0f, 200.0f);

    CHECK(grid.IsWalkable(100.0f, 200.0f), "Vị trí khởi tạo phải đi được");
    CHECK(grid.HasLineOfSight(100.0f, 200.0f, 150.0f, 200.0f), "Đường thẳng trống phải có LoS");

    // Đặt tường chắn ở giữa (125, 200)
    grid.SetBlocked(125.0f, 200.0f, 8.0f);
    CHECK(!grid.IsWalkable(125.0f, 200.0f), "Vị trí vật cản không được đi qua");
    CHECK(!grid.HasLineOfSight(100.0f, 200.0f, 150.0f, 200.0f), "Vật cản phải chặn LoS");

    // Đường vòng né tường (y lệch đi 20 units) phải có LoS
    CHECK(grid.HasLineOfSight(100.0f, 180.0f, 150.0f, 180.0f), "Tia nhìn ngoài vùng chắn phải thông");

    // Test tìm ô đi được gần nhất
    float outX = 0.0f, outY = 0.0f;
    bool found = grid.FindNearestWalkable(125.0f, 200.0f, outX, outY, 30.0f);
    CHECK(found, "Phải tìm được ô walkable gần nhất");
    CHECK(grid.IsWalkable(outX, outY), "Ô tìm được phải walkable");

    // Test Recenter grid khi nhân vật di chuyển xa
    grid.RecenterIfNeeded(600.0f, 700.0f);
    CHECK(std::abs(grid.CenterX() - 600.0f) < 1.0f && std::abs(grid.CenterY() - 700.0f) < 1.0f, "Grid phải recenter theo player");

    std::cout << "  -> TerrainGrid & Bresenham LoS OK" << std::endl;
}

void TestPathfinderAStar() {
    std::cout << "[Test 21] Pathfinder A* & String Pulling Smoothing..." << std::endl;

    navigation::TerrainGrid grid(6.0f);
    grid.Initialize(0.0f, 0.0f);

    // Dựng bức tường chắn ngang từ y = -24 đến y = 24 tại x = 50
    for (float y = -24.0f; y <= 24.0f; y += 4.0f) {
        grid.SetBlocked(50.0f, y, 5.0f);
    }

    CHECK(!grid.HasLineOfSight(0.0f, 0.0f, 100.0f, 0.0f), "Tường chắn phải cản LoS trực tiếp");

    navigation::Pathfinder pathfinder;
    auto rawPath = pathfinder.FindPath({0.0f, 0.0f}, {100.0f, 0.0f}, grid);

    CHECK(!rawPath.empty(), "A* phải tìm được đường vòng qua tường");
    if (!rawPath.empty()) {
        CHECK(rawPath.front().Distance({0.0f, 0.0f}) < 10.0f, "Điểm đầu đường đi phải gần start");
        CHECK(rawPath.back().Distance({100.0f, 0.0f}) < 15.0f, "Điểm cuối đường đi phải gần goal");

        // Kiểm tra tất cả waypoint trên đường đều không dẫm vào ô blocked
        bool allWalkable = true;
        for (const auto& pt : rawPath) {
            if (!grid.IsWalkable(pt.x, pt.y)) {
                allWalkable = false;
                break;
            }
        }
        CHECK(allWalkable, "Tất cả điểm trên đường A* phải đi được");

        // Kiểm tra làm mượt đường (Funnel / String pulling)
        auto smoothPath = pathfinder.SmoothPath(rawPath, grid);
        CHECK(!smoothPath.empty(), "Smooth path không được rỗng");
        CHECK(smoothPath.size() <= rawPath.size(), "Smooth path phải rút gọn số node");
        CHECK(smoothPath.front().Distance({0.0f, 0.0f}) < 10.0f, "Smooth path phải giữ nguyên start");
        CHECK(smoothPath.back().Distance({100.0f, 0.0f}) < 15.0f, "Smooth path phải giữ nguyên goal");
    }

    std::cout << "  -> Pathfinder A* & Smoothing OK" << std::endl;
}

// ==========================================================
// 29. Test Isometric Projection Invariants (Rule 8 & 11)
// ==========================================================
void TestIsometricProjectionInvariants() {
    std::cout << "[Test 29] Isometric Projection Invariants (Rule 8 & 11)..." << std::endl;

    const float screenW = 1920.0f;
    const float screenH = 1080.0f;
    const float cx = screenW * 0.5f; // 960.0f
    const float cy = screenH * 0.5f; // 540.0f

    // 1. Invariant 1: Gốc tọa độ (0, 0) luôn chiếu đúng vào tâm màn hình
    auto pt0 = common::WorldToScreenIsometric(0.0f, 0.0f, 0.0f, screenW, screenH, 50.0f);
    CHECK(std::abs(pt0.x - cx) < 0.01f && std::abs(pt0.y - cy) < 0.01f, "Gốc tọa độ (0,0) phải chiếu đúng tâm màn hình (960, 540)");

    // 2. Invariant 2: Hướng Đông-Bắc (+X, +Y) -> ndx - ndy = 0 -> sx == cx, ndx + ndy > 0 -> sy < cy (hướng lên trên)
    auto ptNE = common::WorldToScreenIsometric(100.0f, 100.0f, 50.0f, screenW, screenH, 50.0f);
    CHECK(std::abs(ptNE.x - cx) < 0.01f, "Vector (+X, +Y) phải có sx == cx (không lệch ngang)");
    CHECK(ptNE.y < cy, "Vector (+X, +Y) phải có sy < cy (hướng lên trên màn hình)");

    // 3. Invariant 3: Hướng Tây-Nam (-X, -Y) -> ndx - ndy = 0 -> sx == cx, ndx + ndy < 0 -> sy > cy (hướng xuống dưới)
    auto ptSW = common::WorldToScreenIsometric(-100.0f, -100.0f, 50.0f, screenW, screenH, 50.0f);
    CHECK(std::abs(ptSW.x - cx) < 0.01f, "Vector (-X, -Y) phải có sx == cx (không lệch ngang)");
    CHECK(ptSW.y > cy, "Vector (-X, -Y) phải có sy > cy (hướng xuống dưới màn hình)");

    // 4. Invariant 4: Hướng Đông-Nam (+X, -Y) -> ndx - ndy > 0 -> sx > cx, ndx + ndy = 0 -> sy == cy (thuần sang phải)
    auto ptSE = common::WorldToScreenIsometric(100.0f, -100.0f, 50.0f, screenW, screenH, 50.0f);
    CHECK(ptSE.x > cx, "Vector (+X, -Y) phải có sx > cx (hướng sang phải)");
    CHECK(std::abs(ptSE.y - cy) < 0.01f, "Vector (+X, -Y) phải có sy == cy (không lệch dọc)");

    // 5. Invariant 5: Hướng Tây-Bắc (-X, +Y) -> ndx - ndy < 0 -> sx < cx, ndx + ndy = 0 -> sy == cy (thuần sang trái)
    auto ptNW = common::WorldToScreenIsometric(-100.0f, 100.0f, 50.0f, screenW, screenH, 50.0f);
    CHECK(ptNW.x < cx, "Vector (-X, +Y) phải có sx < cx (hướng sang trái)");
    CHECK(std::abs(ptNW.y - cy) < 0.01f, "Vector (-X, +Y) phải có sy == cy (không lệch dọc)");

    // 6. Tính toán giá trị số học:
    // Vector (100, -100): ndx = 1/sqrt(2), ndy = -1/sqrt(2)
    // ndx - ndy = 2 / sqrt(2) = sqrt(2) ~ 1.41421356f
    // ptSE.x = cx + 1.41421356f * 50.0f * 0.707f ~ 960 + 49.99 = 1009.99
    CHECK(std::abs(ptSE.x - (cx + 50.0f)) < 1.0f, "Độ lệch ptSE.x xấp xỉ dist (50px)");

    std::cout << "  -> 4 hướng Isometric, invariant tâm màn hình, và độ khớp số học OK" << std::endl;
}

// ==========================================================
// 47. Test Endgame Map Scenarios & Boss Rush Invariants (Rule 8, 9 & 11)
// ==========================================================
void TestEndgameMapScenariosAndBossRush() {
    std::cout << "[Test 47] Endgame Map Scenarios & Boss Rush Invariants (Atlas 2026)..." << std::endl;

    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);

    QuestNavConfig cfg;
    cfg.enabled = true;
    cfg.scenario = MapScenario::BOSS_RUSH;
    cfg.ignoreTrashMobsInRush = true;
    cfg.combatStopDistance = 250.0f;
    cfg.autoPortalAfterBoss = true;
    cfg.postBossLootWaitMs = 2500;
    cfg.stepIntervalMs = 50;

    QuestNavigator qn(cfg);

    TelemetryPacket packet{};
    packet.player.currentHP = 5000;
    packet.player.maxHP = 5000;
    packet.player.posX = 0.0f;
    packet.player.posY = 0.0f;
    packet.player.posZ = 0.0f;

    // 1. Invariant: Trash mob filtering in BOSS_RUSH
    // Put a white trash mob at distance 150u (which is < combatStopDistance 250u)
    packet.entityCount = 1;
    packet.entities[0].id = 101;
    packet.entities[0].type = 1; // Monster
    packet.entities[0].rarity = 0; // White / Normal
    packet.entities[0].extraFlags = 0; // NOT boss
    packet.entities[0].currentHP = 100;
    packet.entities[0].maxHP = 100;
    packet.entities[0].posX = 150.0f;
    packet.entities[0].posY = 0.0f;

    // In BOSS_RUSH, qn should IGNORE trash mob and continue moving towards objective/patrol
    bool moved = qn.Update(packet, kmbox, 1000);
    CHECK(moved, "BOSS_RUSH pháº£i bá» qua quÃ¡i rÃ¡c á»Ÿ cá»± ly 150u vÃ  tiáº¿p tá»¥c di chuyá»ƒn");
    CHECK(!qn.State().bossSpotted, "ChÆ°a phÃ¡t hiá»‡n Boss");

    // ------------------------------------------------------------------
    // RCA (10/09/2026) - 4 kiá»ƒm tra Boss tá»«ng THáº¤T Báº I vÃ¬ test harness sai:
    //   QuestNavigator::Update() Ä‘oáº£n máº¡ch á»Ÿ quest_navigator.cpp:151
    //     if (nowMs - m_state.lastStepMs < effectiveInterval) return false;
    //   vá»›i effectiveInterval bá»‹ CHUáº¨N HÃ“A thÃ nh 650ms (quest_navigator.cpp:147-150)
    //   vÃ¬ moveMode máº·c Ä‘á»‹nh lÃ  MOUSE vÃ  stepIntervalMs (50) <= 120.
    //   Test cÅ© chá»‰ tÄƒng Ä‘á»“ng há»“ 100ms/bÆ°á»›c (1000 -> 1100 -> 1200 -> 1300) nÃªn Má»ŒI
    //   Update sau bÆ°á»›c 1 Ä‘á»u bá»‹ throttle cháº·n TRÆ¯á»šC KHI tá»›i vÃ²ng láº·p phÃ¡t hiá»‡n Boss
    //   (quest_navigator.cpp:306-354) -> bossSpotted/bossSlain/portalSpawned khÃ´ng
    //   bao giá» Ä‘Æ°á»£c thiáº¿t láº­p. BÆ°á»›c 2 "<85u" khi Ä‘Ã³ PASS GIáº¢ (Ä‘Ãºng vÃ¬ throttle,
    //   khÃ´ng pháº£i vÃ¬ rule cháº·n cáº­n chiáº¿n á»Ÿ quest_navigator.cpp:396).
    //   -> Sá»­a gá»‘c rá»…: dÃ¹ng má»‘c thá»i gian tÃ´n trá»ng nhá»‹p 650ms. ÄÃ¢y lÃ  sá»­a TEST
    //      HARNESS, KHÃ”NG Ä‘á»•i hÃ nh vi production.
    // ------------------------------------------------------------------

    // 2. Invariant: Close-range blockage (< 85u) MUST stop even for trash mob
    packet.entities[0].posX = 50.0f; // 50u < 85u
    moved = qn.Update(packet, kmbox, 2000);   // 2000-1000 >= 650ms: qua Ä‘Æ°á»£c throttle
    CHECK(!moved, "BOSS_RUSH pháº£i dá»«ng láº¡i dá»n quÃ¡i rÃ¡c cháº¯n Ä‘Æ°á»ng á»Ÿ cá»± ly gáº§n (< 85u)");

    // 3. Invariant: Boss spotted at distance 200u -> MUST stop to engage Boss!
    packet.entities[0].posX = 200.0f;
    packet.entities[0].rarity = 3; // Unique / Boss
    packet.entities[0].extraFlags = 1; // Boss flag
    std::snprintf(packet.entities[0].name, sizeof(packet.entities[0].name), "%s", "Titan of the Sands");
    moved = qn.Update(packet, kmbox, 3000);
    CHECK(!moved, "BOSS_RUSH pháº£i dá»«ng láº¡i tham chiáº¿n khi phÃ¡t hiá»‡n Map Boss");
    CHECK(qn.State().bossSpotted, "bossSpotted pháº£i Ä‘Æ°á»£c Ä‘Ã¡nh dáº¥u true");
    CHECK(!qn.IsBossSlain(), "Boss chÆ°a bá»‹ háº¡ gá»¥c");

    // 4. Invariant: Boss slain detection
    packet.entities[0].currentHP = 0; // Boss HP = 0
    moved = qn.Update(packet, kmbox, 4000);
    CHECK(qn.IsBossSlain(), "Pháº£i nháº­n diá»‡n Boss Ä‘Ã£ bá»‹ tiÃªu diá»‡t (HP=0)");
    CHECK(qn.State().bossSlainTimestampMs == 4000, "Timestamp Boss slain pháº£i lÃ  4000ms");
    CHECK(!qn.State().portalSpawned, "ChÆ°a Ä‘Æ°á»£c má»Ÿ portal trÆ°á»›c thá»i gian loot");

    // 5. Invariant: Post-Boss looting delay (2.5s = postBossLootWaitMs)
    // Táº¡i t=5000ms (elapsed 1000ms < 2500ms): váº«n pháº£i chá» LootController nháº·t Ä‘á»“
    qn.Update(packet, kmbox, 5000);
    CHECK(!qn.State().portalSpawned, "Trong 2.5s sau khi Boss cháº¿t pháº£i giá»¯ nguyÃªn Ä‘á»ƒ LootController nháº·t Ä‘á»“");

    // Táº¡i t=7000ms (elapsed 3000ms >= 2500ms): PHáº¢I tá»± má»Ÿ Town Portal!
    qn.Update(packet, kmbox, 7000);
    CHECK(qn.State().portalSpawned, "Sau 2.5s looting, pháº£i tá»± Ä‘á»™ng báº¥m 'T' má»Ÿ Town Portal vá» Hideout");
    CHECK(qn.State().mapCompletedCount == 1, "Sá»‘ map hoÃ n thÃ nh pháº£i tÄƒng lÃªn 1");

    // 6. Invariant: Map Scenario hot-switching via SetMapScenario
    qn.SetMapScenario(MapScenario::FAST_CLEAR, false);
    CHECK(qn.Config().scenario == MapScenario::FAST_CLEAR, "SetMapScenario pháº£i Ä‘á»•i sang FAST_CLEAR");
    CHECK(!qn.Config().autoPortalAfterBoss, "autoPortalAfterBoss pháº£i lÃ  false");

    qn.SetMapScenario(MapScenario::ATLAS_QUEST_RUSH, true);
    CHECK(qn.Config().scenario == MapScenario::ATLAS_QUEST_RUSH, "SetMapScenario pháº£i Ä‘á»•i sang ATLAS_QUEST_RUSH");
    CHECK(qn.Config().autoPortalAfterBoss, "autoPortalAfterBoss pháº£i lÃ  true");

    // 7. Invariant: ResetMapState
    qn.ResetMapState();
    CHECK(!qn.State().bossSpotted, "ResetMapState pháº£i reset bossSpotted");
    CHECK(!qn.State().bossSlain, "ResetMapState pháº£i reset bossSlain");
    CHECK(!qn.State().portalSpawned, "ResetMapState pháº£i reset portalSpawned");

    std::cout << "  -> Endgame Map Scenarios & Boss Rush Invariants OK" << std::endl;
}

// ==========================================================
// 74. Jump Point Search (JPS) Pathfinder & Any-Angle Smoothing (Doc 32 & Doc 38)
// ==========================================================
void TestJumpPointSearchPathfinder() {
    std::cout << "[Test 74] Jump Point Search (JPS) Pathfinder & Any-Angle Smoothing..." << std::endl;

    navigation::TerrainGrid grid(6.0f);
    grid.Initialize(0.0f, 0.0f);

    // Dựng bức tường chắn ngang từ y = -36 đến y = 36 tại x = 60
    for (float y = -36.0f; y <= 36.0f; y += 4.0f) {
        grid.SetBlocked(60.0f, y, 6.0f);
    }

    CHECK(!grid.HasLineOfSight(0.0f, 0.0f, 120.0f, 0.0f), "Tuong chan phai can LoS truc tiep giua (0,0) va (120,0)");

    navigation::Pathfinder pathfinder;

    // 1. Chạy A* truyền thống để lấy baseline
    auto astarPath = pathfinder.FindPathAStar({0.0f, 0.0f}, {120.0f, 0.0f}, grid);
    uint32_t astarNodes = pathfinder.LastNodesExplored();
    float astarTimeUs = pathfinder.LastComputeTimeUs();
    CHECK(!astarPath.empty(), "A* phai tim duoc duong vong qua tuong");

    // 2. Chạy JPS tốc độ cao
    auto jpsPath = pathfinder.FindPathJPS({0.0f, 0.0f}, {120.0f, 0.0f}, grid);
    uint32_t jpsNodes = pathfinder.LastNodesExplored();
    float jpsTimeUs = pathfinder.LastComputeTimeUs();

    CHECK(!jpsPath.empty(), "JPS phai tim duoc duong vong qua tuong");
    CHECK(jpsPath.front().Distance({0.0f, 0.0f}) < 15.0f, "Diem dau duong JPS phai gan start");
    CHECK(jpsPath.back().Distance({120.0f, 0.0f}) < 15.0f, "Diem cuoi duong JPS phai gan goal");

    // Khẳng định Invariant INV-NAV-02: Moi waypoint tren duong JPS khong duoc dam vao o blocked
    bool allWalkable = true;
    for (const auto& pt : jpsPath) {
        if (!grid.IsWalkable(pt.x, pt.y)) {
            allWalkable = false;
            break;
        }
    }
    CHECK(allWalkable, "Moi diem tren duong JPS phai hoan toan di duoc (walkable)");

    // Khẳng định Invariant INV-NAV-01: JPS tiet kiem it nhat 60% so node duyet so voi A*
    std::cout << "  [Benchmark] A* nodes: " << astarNodes << " (" << astarTimeUs << " us) vs JPS nodes: "
              << jpsNodes << " (" << jpsTimeUs << " us)" << std::endl;
    CHECK(jpsNodes <= astarNodes, "JPS phai duyet it hon hoac bang so node cua A* truyen thong");
    CHECK(jpsTimeUs < 1000.0f, "Thoi gian tinh toan JPS phai < 1ms (1000 us)");

    // 3. Kiem tra tinh tuong thich FindPath() mac dinh la JPS
    pathfinder.SetAlgorithm(navigation::PathAlgorithm::JPS_FAST);
    auto defaultPath = pathfinder.FindPath({0.0f, 0.0f}, {120.0f, 0.0f}, grid);
    CHECK(!defaultPath.empty(), "FindPath() mac dinh phai tim duoc duong");

    std::cout << "  -> Jump Point Search (JPS) Pathfinder & Smoothing (Test 74) OK" << std::endl;
}

// ==========================================================
// 75. Exploration Heatmap & Frontier Momentum 2.0 (Doc 38)
// ==========================================================
void TestExplorationHeatmapAndFrontier() {
    std::cout << "[Test 75] Exploration Heatmap & Frontier Momentum 2.0..." << std::endl;

    QuestNavConfig cfg;
    cfg.enabled = true;
    cfg.autoPatrol = true;
    QuestNavigator qn(cfg);

    // 1. Khoi tao chua co du lieu
    CHECK(qn.TotalTilesVisited() == 0, "Ban dau Heatmap phai chua co tile nao");
    CHECK(qn.GetVisitCount(100.0f, 100.0f) == 0, "Tile chua di qua phai co visitCount = 0");

    // 2. Ghi nhan vi tri
    qn.RecordPosition(100.0f, 100.0f);
    CHECK(qn.TotalTilesVisited() == 1, "Sau khi record 1 vi tri, totalTilesVisited phai = 1");
    CHECK(qn.GetVisitCount(100.0f, 100.0f) == 1, "VisitCount phai = 1");

    // Ghi nhan tiep tuc cung vi tri
    qn.RecordPosition(100.0f, 100.0f);
    CHECK(qn.GetVisitCount(100.0f, 100.0f) == 2, "VisitCount phai tang len 2");
    CHECK(qn.TotalTilesVisited() == 1, "TotalTilesVisited van phai giu nguyen 1");

    // Ghi nhan vi tri khac cach 100 units
    qn.RecordPosition(200.0f, 200.0f);
    CHECK(qn.TotalTilesVisited() == 2, "TotalTilesVisited phai tang len 2");
    CHECK(qn.GetVisitCount(200.0f, 200.0f) == 1, "Tile moi phai co visitCount = 1");

    // 3. Reset Heatmap khi sang map moi
    qn.ResetHeatmap();
    CHECK(qn.TotalTilesVisited() == 0, "Sau khi ResetHeatmap, totalTilesVisited phai = 0");
    CHECK(qn.GetVisitCount(100.0f, 100.0f) == 0, "Sau khi ResetHeatmap, visitCount phai = 0");
    CHECK(qn.GetExplorationCoverage() == 0.0f, "Coverage phai = 0 khi chua visit");
    CHECK(!qn.IsMapFullyExplored(), "Chua tham map thi IsMapFullyExplored phai = false");

    // 4. Mo rong visit de kiem tra Exploration Coverage
    for (int i = 0; i < 180; ++i) {
        qn.RecordPosition(static_cast<float>((i % 20) * 35), static_cast<float>((i / 20) * 35));
    }
    CHECK(qn.GetExplorationCoverage() >= 0.90f, "Coverage phai >= 90% khi visit 180 o khac nhau");
    CHECK(qn.IsMapFullyExplored(0.90f), "IsMapFullyExplored(0.90) phai = true khi dat nguong");

    std::cout << "  -> Exploration Heatmap & Frontier Momentum 2.0 (Test 75) OK" << std::endl;
}

// ==========================================================
// 76. FAST_CLEAR Pack Clustering & ATLAS_QUEST_RUSH Event Steering (Doc 25 & Doc 38)
// ==========================================================
void TestScenarioFastClearAndAtlasRush() {
    std::cout << "[Test 76] FAST_CLEAR Pack Clustering & ATLAS_QUEST_RUSH Event Steering..." << std::endl;

    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);

    // ----------------------------------------------------
    // PHẦN 1: FAST_CLEAR PACK CLUSTERING
    // ----------------------------------------------------
    QuestNavConfig fastCfg;
    fastCfg.enabled = true;
    fastCfg.scenario = MapScenario::FAST_CLEAR;
    fastCfg.combatStopDistance = 300.0f;
    fastCfg.stepIntervalMs = 50;
    fastCfg.fastMoveIntervalMs = 200;

    QuestNavigator qnFast(fastCfg);

    TelemetryPacket packet{};
    packet.player.currentHP = 4000;
    packet.player.maxHP = 4000;
    packet.player.posX = 0.0f;
    packet.player.posY = 0.0f;
    packet.player.posZ = 0.0f;

    // Kịch bản A: Chi co 1 quai trang o cu ly 150u (< 300u)
    // Invariant INV-NAV-03: FAST_CLEAR phai bo qua 1 quai trang le loi, tiep tuc di chuyen
    packet.entityCount = 1;
    packet.entities[0].id = 201;
    packet.entities[0].type = 1;
    packet.entities[0].rarity = 0; // Normal / White
    packet.entities[0].extraFlags = 0;
    packet.entities[0].currentHP = 100;
    packet.entities[0].maxHP = 100;
    packet.entities[0].posX = 150.0f;
    packet.entities[0].posY = 0.0f;

    bool moved = qnFast.Update(packet, kmbox, 1000);
    CHECK(moved, "FAST_CLEAR phai bo qua 1 quai trang le loi va tiep tuc di chuyen");

    // Kịch bản B: Chi co 2 quai trang o cu ly 150u va 160u -> van < 3 quai -> van tiep tuc di chuyen
    packet.entityCount = 2;
    packet.entities[1].id = 202;
    packet.entities[1].type = 1;
    packet.entities[1].rarity = 0;
    packet.entities[1].extraFlags = 0;
    packet.entities[1].currentHP = 100;
    packet.entities[1].maxHP = 100;
    packet.entities[1].posX = 160.0f;
    packet.entities[1].posY = 0.0f;

    moved = qnFast.Update(packet, kmbox, 2000);
    CHECK(moved, "FAST_CLEAR phai bo qua 2 quai trang (< 3 quai) de duy tri toc do");

    // Kịch bản C: Cum co 3 quai trang (Pack Count >= 3) -> PHAI DUNG LAI XA SKILL!
    packet.entityCount = 3;
    packet.entities[2].id = 203;
    packet.entities[2].type = 1;
    packet.entities[2].rarity = 0;
    packet.entities[2].extraFlags = 0;
    packet.entities[2].currentHP = 100;
    packet.entities[2].maxHP = 100;
    packet.entities[2].posX = 170.0f;
    packet.entities[2].posY = 0.0f;

    moved = qnFast.Update(packet, kmbox, 3000);
    CHECK(!moved, "FAST_CLEAR phai dung lai khi gap cum tu 3 quai trang tro len");

    // Kịch bản D: Chi co 1 quai nhung la quai RARE (rarity == 2) -> PHAI DUNG LAI XA SKILL!
    packet.entityCount = 1;
    packet.entities[0].rarity = 2; // Rare mob
    moved = qnFast.Update(packet, kmbox, 4000);
    CHECK(!moved, "FAST_CLEAR phai dung lai ngay lap tuc khi gap quai Rare tren duong");

    // ----------------------------------------------------
    // PHẦN 2: ATLAS_QUEST_RUSH EVENT STEERING
    // ----------------------------------------------------
    QuestNavConfig atlasCfg;
    atlasCfg.enabled = true;
    atlasCfg.scenario = MapScenario::ATLAS_QUEST_RUSH;
    atlasCfg.stepIntervalMs = 50;
    atlasCfg.fastMoveIntervalMs = 200;

    QuestNavigator qnAtlas(atlasCfg);
    AreaEventManager areaEventMgr;
    qnAtlas.SetAreaEventManager(&areaEventMgr);

    packet.entityCount = 0;
    moved = qnAtlas.Update(packet, kmbox, 5000);
    CHECK(moved, "ATLAS_QUEST_RUSH khi chua co event se tuan tra do map");

    // Giả lập phát hiện sự kiện Breach / Citadel trên radar
    AreaEventRecord evtRecord{};
    evtRecord.entityId = 555;
    evtRecord.type = 5; // Breach / Event entity
    evtRecord.x = 250.0f;
    evtRecord.y = 150.0f;
    evtRecord.status = AreaEventStatus::Candidate;
    std::snprintf(evtRecord.name, sizeof(evtRecord.name), "%s", "Breach Hand of Chayula");
    areaEventMgr.AddOrUpdateEvent(evtRecord);

    // Invariant INV-NAV-04: qnAtlas phai khoa muc tieu la Event nay va dieu huong toi event
    moved = qnAtlas.Update(packet, kmbox, 6000);
    CHECK(moved, "ATLAS_QUEST_RUSH phai di chuyen toi toa do event");
    CHECK(qnAtlas.State().targetEntityId == 555, "Muc tieu phai la Entity ID cua Event 555");
    CHECK(qnAtlas.State().targetName.find("Breach Hand of Chayula") != std::string::npos, "TargetName phai chua ten Event");

    // Khi di chuyen toi sat event (cự ly <= interactRadius 15.0f)
    packet.player.posX = 245.0f;
    packet.player.posY = 150.0f;
    moved = qnAtlas.Update(packet, kmbox, 7000);
    CHECK(moved, "Phai tuong tac voi event khi toi gan");
    CHECK(qnAtlas.State().questsProgressed >= 1, "Tien do quest/event phai tang len");

    std::cout << "  -> FAST_CLEAR Pack Clustering & ATLAS_QUEST_RUSH Event Steering (Test 76) OK" << std::endl;
}

// ==========================================================
// Test WorldToWasd Isometric Invariants (Rule 8 & 11)
// ==========================================================
void TestWorldToWasdIsometricInvariants() {
    std::cout << "[Test 79] WorldToWasd Isometric Invariants (8 directions & deadzone)..." << std::endl;

    // 1. Hướng W (Lên / Đông-Bắc trong world: +X, +Y)
    auto wasdW = common::WorldToWasd(10.0f, 10.0f);
    CHECK(wasdW.up && !wasdW.down && !wasdW.left && !wasdW.right, "World (+X, +Y) phai la huong W (Up)");

    // 2. Hướng S (Xuống / Tây-Nam trong world: -X, -Y)
    auto wasdS = common::WorldToWasd(-10.0f, -10.0f);
    CHECK(wasdS.down && !wasdS.up && !wasdS.left && !wasdS.right, "World (-X, -Y) phai la huong S (Down)");

    // 3. Hướng D (Phải / Đông-Nam trong world: +X, -Y)
    auto wasdD = common::WorldToWasd(10.0f, -10.0f);
    CHECK(wasdD.right && !wasdD.left && !wasdD.up && !wasdD.down, "World (+X, -Y) phai la huong D (Right)");

    // 4. Hướng A (Trái / Tây-Bắc trong world: -X, +Y)
    auto wasdA = common::WorldToWasd(-10.0f, 10.0f);
    CHECK(wasdA.left && !wasdA.right && !wasdA.up && !wasdA.down, "World (-X, +Y) phai la huong A (Left)");

    // 5. Hướng W+D (Chéo Trên-Phải / Đông trong world: +X, 0)
    auto wasdWD = common::WorldToWasd(10.0f, 0.0f);
    CHECK(wasdWD.up && wasdWD.right && !wasdWD.down && !wasdWD.left, "World (+X, 0) phai la to hop W+D");

    // 6. Hướng W+A (Chéo Trên-Trái / Bắc trong world: 0, +Y)
    auto wasdWA = common::WorldToWasd(0.0f, 10.0f);
    CHECK(wasdWA.up && wasdWA.left && !wasdWA.down && !wasdWA.right, "World (0, +Y) phai la to hop W+A");

    // 7. Hướng S+D (Chéo Dưới-Phải / Nam trong world: 0, -Y)
    auto wasdSD = common::WorldToWasd(0.0f, -10.0f);
    CHECK(wasdSD.down && wasdSD.right && !wasdSD.up && !wasdSD.left, "World (0, -Y) phai la to hop S+D");

    // 8. Hướng S+A (Chéo Dưới-Trái / Tây trong world: -X, 0)
    auto wasdSA = common::WorldToWasd(-10.0f, 0.0f);
    CHECK(wasdSA.down && wasdSA.left && !wasdSA.up && !wasdSA.right, "World (-X, 0) phai la to hop S+A");

    // 9. Kiểm tra Deadzone
    auto wasdDead = common::WorldToWasd(1.0f, 1.0f, 2.0f);
    CHECK(wasdDead.empty(), "Vector trong deadzone (< 2.0f) phai tra ve empty");
    auto wasdZero = common::WorldToWasd(0.0f, 0.0f);
    CHECK(wasdZero.empty(), "Vector (0, 0) phai tra ve empty");

    // 10. Kiting ReflexManager WASD Integration
    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);
    ReflexConfig cfg;
    cfg.moveMode = MovementMode::WASD;
    ReflexManager rm(cfg);
    rm.TriggerKiting(-10.0f, -10.0f, kmbox, 12345);
    CHECK(rm.State().lastKiteMs == 12345, "TriggerKiting phai cap nhat lastKiteMs voi moveMode=WASD");

    std::cout << "  -> 8 huong WASD isometric, deadzone va ReflexManager integration OK" << std::endl;
}

// ==========================================================
// Test 87: QuestNavigator Cold-Start Probing & Inventory Resolution Invariants
// ==========================================================
void TestQuestNavigatorColdStartProbingAndInventoryInvariants() {
    std::cout << "[Test 87] QuestNavigator Cold-Start Probing & Inventory Resolution Invariants..." << std::endl;

    // 1. QuestNavigator Cold-Start Probing Step
    // Khi moi vao map chua co XYZ (!hasValidXYZ) va kmbox da ket noi / bat emulation mode,
    // QuestNavigator phai phat buoc di tham do (WAITING_FIRST_STEP) de PlayerFinder bat vi sai toa do.
    QuestNavConfig qcfg;
    qcfg.enabled = true;
    qcfg.autoPatrol = true;
    qcfg.moveMode = MovementMode::WASD;

    QuestNavigator qn(qcfg);

    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);
    kmbox.EnableEmulationMode(true);
    CHECK(kmbox.IsConnected(), "KMBox phai o trang thai connected khi bat emulation mode");

    TelemetryPacket pktColdStart{};
    pktColdStart.player.maxHP = 2000;
    pktColdStart.player.currentHP = 2000;
    pktColdStart.player.posX = 0.0f;
    pktColdStart.player.posY = 0.0f;
    pktColdStart.player.posZ = 0.0f; // Invalid XYZ -> hasValidXYZ = false

    bool stepped = qn.Update(pktColdStart, kmbox, 1000);
    CHECK(stepped, "QuestNavigator phai thuc hien buoc tham do Cold-Start khi !hasValidXYZ");
    CHECK(qn.State().targetName == "Cold-Start Probing Step (WAITING_FIRST_STEP)", 
          "TargetName phai phan anh buoc tham do Cold-Start WAITING_FIRST_STEP");

    // 2. Inventory Resolution Invariants (Aspect Ratio 1:1 theo truc doc)
    InventoryCoordinates invCoords;
    // Tren man hinh Ultrawide 2560x1080 vs 1920x1080 (cung chieu cao 1080p):
    // Vi chi scale theo chieu doc (screenH / 1080.0f), toa do Y va chieu cao o phai giong het nhau
    auto cell1080 = invCoords.GetInventoryCell(0, 0, 1920.0f, 1080.0f);
    auto cellUltrawide = invCoords.GetInventoryCell(0, 0, 2560.0f, 1080.0f);
    CHECK(std::abs(cell1080.x - cellUltrawide.x) < 0.001f, "Toa do X cua inventory cell phai khong bi meo do screenW");
    CHECK(std::abs(cell1080.y - cellUltrawide.y) < 0.001f, "Toa do Y cua inventory cell phai khop tuyet doi giua 16:9 va 21:9");

    std::cout << "  -> QuestNavigator Cold-Start Probing & Inventory Resolution Invariants OK" << std::endl;
}

// ==========================================================
// Test 89: Tangent Vector Sliding & Obstacle Avoidance (INV-NAV-TERRAIN-COMMERCIAL)
// ==========================================================
void TestTangentSlideAndObstacleAvoidanceInvariants() {
    std::cout << "[Test 89] Tangent Vector Sliding & Obstacle Avoidance (INV-NAV-TERRAIN-COMMERCIAL)..." << std::endl;

    navigation::Pathfinder pf;

    // 1. Kiểm tra trường hợp zero displacement -> trả về isSliding == false
    auto zeroSlide = pf.ComputeTangentSlide(
        navigation::Vec2(0.0f, 0.0f),
        navigation::Vec2(0.0f, 0.0f),
        navigation::Vec2(10.0f, 10.0f)
    );
    CHECK(!zeroSlide.isSliding, "Zero displacement khong duoc phep trigger sliding");

    // 2. Kiểm tra hình học tiếp tuyến:
    // Hướng mong muốn thẳng sang phải: desiredDir = (10, 0), mục tiêu ở (10, 10) (góc phần tư 1, Y > 0)
    // Tiếp tuyến trái CCW: tLeft = (-ndy, ndx) = (0, 1) (hướng Y dương)
    // Tiếp tuyến phải CW: tRight = (ndy, -ndx) = (0, -1) (hướng Y âm)
    // Mục tiêu toGoal có Y dương -> dotLeft > dotRight -> Left hand (handDirection == 1) được chọn!
    auto slideLeft = pf.ComputeTangentSlide(
        navigation::Vec2(0.0f, 0.0f),
        navigation::Vec2(10.0f, 0.0f),
        navigation::Vec2(10.0f, 10.0f),
        nullptr,
        0
    );
    CHECK(slideLeft.isSliding, "Phai trigger tangent sliding");
    CHECK(slideLeft.handDirection == 1, "Phai chon Left hand (1) vi huong ve muc tieu co Y > 0");
    CHECK(slideLeft.slideVector.y > 0.0f, "Vector truot phai co thanh phan Y > 0 men theo left tangent");
    CHECK(slideLeft.slideVector.x > 0.0f, "Vector truot phai giu thanh phan X > 0 (25% blend)");

    // 3. Kiểm tra duy trì preferredHand (Wall-following hysteresis)
    auto slideFollow = pf.ComputeTangentSlide(
        navigation::Vec2(0.0f, 0.0f),
        navigation::Vec2(10.0f, 0.0f),
        navigation::Vec2(10.0f, 10.0f),
        nullptr,
        -1 // Force preferredHand = -1 (Right)
    );
    CHECK(slideFollow.handDirection == -1, "Phai duy tri preferredHand == -1");
    CHECK(slideFollow.slideVector.y < 0.0f, "Vector truot phai men theo right tangent (Y < 0)");

    // 4. Kiểm tra tích hợp TerrainGrid tránh ô bị chặn (Block Avoidance)
    navigation::TerrainGrid grid(5.0f);
    grid.Initialize(50.0f, 50.0f);
    // currentPos = (50, 50), desiredDir = (5, 0)
    // Left tangent đi về phía (50, 60). Ta chặn ô tại (50, 60)
    grid.SetBlocked(50.0f, 60.0f, 6.0f);
    // Right tangent đi về phía (50, 40) - vẫn walkable
    auto slideGrid = pf.ComputeTangentSlide(
        navigation::Vec2(50.0f, 50.0f),
        navigation::Vec2(5.0f, 0.0f),
        navigation::Vec2(50.0f, 70.0f), // Goal ở phía left, nhung left bi chan tren grid!
        &grid,
        0
    );
    CHECK(slideGrid.isSliding, "Phai trigger sliding voi TerrainGrid");
    CHECK(slideGrid.handDirection == -1, "Phai tu dong ne sang Right hand vi phia Left da bi blocked tren Grid");

    // 5. Kiểm tra QuestNavigator micro-stuck tracking (Delta XYZ ~ 0)
    QuestNavConfig navCfg{};
    navCfg.enabled = true;
    navCfg.interactRadius = 15.0f;
    navCfg.stepIntervalMs = 50;
    QuestNavigator qn(navCfg);

    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);
    kmbox.EnableEmulationMode(true);

    TelemetryPacket pkt{};
    pkt.player.currentHP = 1000;
    pkt.player.maxHP = 1000;
    pkt.player.posX = 100.0f;
    pkt.player.posY = 120.0f;
    pkt.player.posZ = 15.0f;
    pkt.entityCount = 1;
    pkt.entities[0].id = 888;
    pkt.entities[0].type = 3;
    pkt.entities[0].posX = 200.0f;
    pkt.entities[0].posY = 120.0f;
    pkt.entities[0].posZ = 15.0f;
    std::snprintf(pkt.entities[0].name, sizeof(pkt.entities[0].name), "%s", "Distant Waypoint");

    // Tick 1 (1000ms): Khoi tao buoc di chuyen
    qn.Update(pkt, kmbox, 1000);
    CHECK(qn.State().tangentSlideCount == 0, "Chua bi stuck o tick 1");

    // Tick 2 (1400ms > 350ms): Player chi nhich 0.2 đơn vị (moveDistSq < 1.5 -> micro-stuck!)
    pkt.player.posX = 100.2f;
    pkt.player.posY = 120.1f;
    qn.Update(pkt, kmbox, 1400);
    CHECK(qn.State().tangentSlideCount >= 1, "Phai phat hien micro-stuck va tang tangentSlideCount");
    CHECK(qn.State().tangentHand != 0, "Phai chon tangent hand de truot");

    std::cout << "  -> Tangent Vector Sliding & Obstacle Avoidance (INV-NAV-TERRAIN-COMMERCIAL) OK" << std::endl;
}

