// ==========================================================
// Domain 3: Combat & Reflex Unit Tests
// ==========================================================

#include "test_harness.hpp"

#include "common/protocol.hpp"
#include "ipc/shared_memory.hpp"
#include "input/human_curve.hpp"
#include "input/non_blocking_actuator.hpp"
#include "navigation/movement_controller.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 "combat/stutter_step_controller.hpp"
#include "combat/boss_phase_controller.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>

// ==========================================================
// 2. Động cơ quỹ đạo chuột sinh học
// ==========================================================
void TestHumanCurve() {
    std::cout << "[Test 2] Quỹ đạo chuột Bézier sinh học..." << std::endl;

    HumanCurveGenerator gen;

    // 2a. Tổng các bước rời rạc phải đạt đúng đích (dung sai làm tròn)
    bool allTrialsOk = true;
    for (int trial = 0; trial < 50; ++trial) {
        const float sx = static_cast<float>(trial * 7 % 400);
        const float sy = static_cast<float>(trial * 13 % 300);
        const float tx = sx + 200.0f + static_cast<float>(trial * 3 % 600);
        const float ty = sy + 150.0f + static_cast<float>(trial * 5 % 400);

        auto steps = gen.GenerateTrajectory({ sx, sy }, { tx, ty });
        if (steps.empty()) { allTrialsOk = false; break; }

        float sumX = 0.0f, sumY = 0.0f;
        for (const auto& s : steps) { sumX += s.x; sumY += s.y; }

        if (std::fabs(sumX - (tx - sx)) > 2.0f || std::fabs(sumY - (ty - sy)) > 2.0f) {
            allTrialsOk = false;
            break;
        }
    }
    CHECK(allTrialsOk, "Quỹ đạo chuột Bézier phải hội tụ chính xác về đích cho mọi điểm");


    // 2b. Dwell time nằm trong biên sinh học [35, 250] ms
    bool dwellOk = true;
    for (int i = 0; i < 200; ++i) {
        const int dwell = gen.GenerateDwellTimeMs();
        if (dwell < 35 || dwell > 250) { dwellOk = false; break; }
    }
    CHECK(dwellOk, "Dwell time vượt biên [35, 250] ms");

    // 2c. Nhịp sinh học có tính ngẫu nhiên (hai quỹ đạo không giống hệt nhau)
    auto a = gen.GenerateTrajectory({ 0, 0 }, { 900, 700 });
    auto b = gen.GenerateTrajectory({ 0, 0 }, { 900, 700 });
    bool identical = (a.size() == b.size());
    if (identical) {
        for (size_t i = 0; i < a.size(); ++i) {
            if (a[i].x != b[i].x || a[i].y != b[i].y) { identical = false; break; }
        }
    }
    CHECK(!identical, "Hai quỹ đạo giống hệt nhau - thiếu nhiễu sinh học");

    std::cout << "  -> 100 quỹ đạo đạt đích chính xác, dwell-time hợp lệ, có nhiễu" << std::endl;
}

// ==========================================================
// 14. Bộ Điều Phối Phản Xạ Sinh Tồn & Chiến Đấu (ReflexManager)
//  - Iframe Dodge Roll (Space)
//  - Smart Flask Sipping (2 bình Máu & Mana chuẩn POE2)
//  - Dynamic Kiting (Thả diều quái theo tọa độ XYZ)
// ==========================================================
void TestReflexManager() {
    std::cout << "[Test 14] ReflexManager - Iframe Dodge, Smart Flask, Kiting XYZ..." << std::endl;
    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.kitingEnabled = true;
    cfg.safeDistance = 35.0f;
    cfg.kitingIntervalMs = 100;
    cfg.emergencyLogoutEnabled = false;

    ReflexManager rm(cfg);

    TelemetryPacket packet{};
    packet.player.maxHP = 1000;
    packet.player.currentHP = 1000;
    packet.player.maxMana = 500;
    packet.player.currentMana = 500;
    packet.player.posX = 100.0f;
    packet.player.posY = 100.0f;
    packet.player.posZ = 0.0f;

    rm.Update(packet, kmbox, 1000);
    CHECK(rm.State().dodgeCount == 0 && rm.State().lifeUses == 0, "Dodge count & life uses ban đầu phải bằng 0");

    // 1. Iframe Dodge Roll khi bị Burst Damage (1000 -> 750)
    packet.player.currentHP = 750;
    rm.Update(packet, kmbox, 1050);
    CHECK(rm.State().pendingDodge && rm.State().dodgeCount == 0, "Burst damage phải lên lịch né có độ trễ sinh học");

    rm.Update(packet, kmbox, 1100);
    rm.Update(packet, kmbox, 1210);
    CHECK(rm.State().dodgeCount == 1 && !rm.State().pendingDodge, "Dodge roll phải kích hoạt sau độ trễ");

    packet.player.currentHP = 500;
    rm.Update(packet, kmbox, 1400);
    CHECK(rm.State().dodgeCount == 1, "Dodge roll phải tuân thủ Cooldown 800ms");

    packet.player.currentHP = 200;
    rm.Update(packet, kmbox, 2100);
    rm.Update(packet, kmbox, 2260);
    CHECK(rm.State().dodgeCount == 2, "Dodge roll phải kích hoạt lại sau cooldown và delay");

    bool dwellOk = true;
    for (int i = 0; i < 50; ++i) {
        uint32_t d = rm.GenerateGaussianDwellTime(48.0f, 7.0f, 35.0f, 65.0f);
        if (d < 35 || d > 65) { dwellOk = false; break; }
    }
    CHECK(dwellOk, "Gaussian dwell time trong ReflexManager phải nằm trong [35, 65]ms");

    // 2. Smart Life Flask HoT & Chống spam bình
    ReflexManager rmFlask(cfg);
    packet.player.currentHP = 500;
    rmFlask.Update(packet, kmbox, 1000);
    CHECK(rmFlask.State().lifeUses == 1 && rmFlask.State().isLifeHoT, "Bình máu phải được uống tại 50% HP");

    packet.player.currentHP = 540;
    rmFlask.Update(packet, kmbox, 1900);
    CHECK(rmFlask.State().lifeUses == 1, "Không được spam bình máu khi máu đang tăng (HoT)");

    packet.player.currentHP = 250;
    rmFlask.Update(packet, kmbox, 2000);
    CHECK(rmFlask.State().lifeUses == 2, "Phải uống tiếp bình máu khi HP rơi vào nguy cấp");

    // 3. Smart Mana Flask & Phát hiện cạn sạc bình
    ReflexManager rmMana(cfg);
    packet.player.currentHP = 1000;
    packet.player.currentMana = 100;
    rmMana.Update(packet, kmbox, 1000);
    rmMana.Update(packet, kmbox, 1700);
    rmMana.Update(packet, kmbox, 2800);
    rmMana.Update(packet, kmbox, 3500);
    rmMana.Update(packet, kmbox, 4600);
    rmMana.Update(packet, kmbox, 5300);
    rmMana.Update(packet, kmbox, 6400);
    CHECK(rmMana.State().manaUses == 3 && rmMana.State().manaEmpty, "Mana empty detection sau 3 lần không tác dụng failed");

    // 4. Dynamic Kiting & Auto-Respawn
    ReflexManager rmKite(cfg);
    packet.player.posX = 100.0f;
    packet.player.posY = 100.0f;
    packet.entityCount = 1;
    packet.entities[0].type = 1;
    packet.entities[0].extraFlags = 0;
    packet.entities[0].posX = 110.0f;
    packet.entities[0].posY = 100.0f;
    rmKite.Update(packet, kmbox, 1000);
    CHECK(rmKite.State().lastKiteMs == 1000, "Dynamic Kiting phải kích hoạt khi quái áp sát");

    ReflexManager rmRespawn(cfg);
    packet.player.currentHP = 0;
    rmRespawn.Update(packet, kmbox, 1000);
    rmRespawn.Update(packet, kmbox, 2500);
    rmRespawn.Update(packet, kmbox, 3300);
    CHECK(rmRespawn.State().deathTimestampMs == 1000 && rmRespawn.State().totalRespawns == 1,
          "Auto-Respawn sau 2.2s delay failed");

    std::cout << "  -> Iframe Dodge, Smart Flask, Kiting & Auto-Respawn OK" << std::endl;
}

// ==========================================================
// 16. ComboManager (Rotation & Stagger Finisher)
// ==========================================================
void TestComboManager() {
    std::cout << "[Test 16] ComboManager - Skill Dynamic Distance & Stagger Finisher..." << std::endl;
    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);

    ComboConfig cfg;
    cfg.isRangedMode = false;
    cfg.primarySkillVk = VK_LBUTTON;
    cfg.autoStaggerFinisher = true;
    cfg.staggerThreshold = 8000;
    cfg.comboIntervalMs = 100;
    cfg.minManaToCombo = 10;
    cfg.closeRangeDistance = 180.0f;
    cfg.closeSkillVk = 0x45;        // 'E'
    cfg.rangedSkillVk = VK_RBUTTON; // RMB
    cfg.useMonkRotation = false;

    ComboManager cm(cfg);

    TelemetryPacket packet{};
    packet.player.currentHP = 100;
    packet.player.maxHP = 100;
    packet.player.currentMana = 50;
    packet.player.maxMana = 100;
    packet.player.posX = 0.0f;
    packet.player.posY = 0.0f;

    // Quái vật ở gần (cự ly 10 <= 180) -> Tung chiêu 'E'
    packet.entityCount = 1;
    packet.entities[0].type = 1; // Monster
    packet.entities[0].extraFlags = 0; // Alive
    packet.entities[0].posX = 10.0f;
    packet.entities[0].posY = 0.0f;
    packet.entities[0].staggerProgress = 2000; // Chưa bị stagger

    // Tick 1: Quái gần (cự ly 10 <= 180) -> Dùng chiêu E
    CHECK(cm.Update(packet, kmbox, 1000), "Phai tung skill quai gan");
    CHECK(cm.State().totalCombosExecuted == 1, "Tong combo phai la 1");
    CHECK(cm.State().currentRotationStep == 1, "Rotation phai chuyen sang buoc 1");

    // Tick 2: Quái ở xa (cự ly 250 > 180) -> Dùng chiêu Chuột Phải
    packet.entities[0].posX = 250.0f;
    CHECK(cm.Update(packet, kmbox, 1200), "Phai tung skill quai xa");
    CHECK(cm.State().totalCombosExecuted == 2, "Tong combo phai la 2");
    CHECK(cm.State().currentRotationStep == 0, "Rotation phai quay ve buoc 0");

    // Kiểm thử Stagger Finisher: thanh Stagger của quái đạt 90% (9000 >= 8000)
    packet.entities[0].posX = 50.0f;
    packet.entities[0].staggerProgress = 9000;
    CHECK(cm.Update(packet, kmbox, 2000), "Phai kich hoat Stagger Finisher");
    CHECK(cm.State().totalFinishersExecuted == 1, "Tong finisher phai la 1");

    // Kiểm thử cơ chế Tự Vệ Khi Bị Đánh (Under Attack Defense):
    // Radar không thấy quái nhưng player vừa bị mất 30 HP
    packet.entityCount = 0;
    packet.player.currentHP = 70; // Giảm từ 100 xuống 70
    CHECK(cm.Update(packet, kmbox, 2200), "Phai kich hoat phan xa tu ve khi bi danh");
    CHECK(cm.State().inCombatUntilMs >= 2200 + 3000, "Phai duy tri inCombatUntilMs trong 3s");
    CHECK(cm.State().totalCombosExecuted == 3, "Tong combo phai tang len 3");

    std::cout << "  -> Skill Selection (Quai gan [E] vs Quai xa [RMB]), Stagger Finisher & Combat Stance OK" << std::endl;
}

void TestKitingEngine() {
    std::cout << "[Test 22] KitingEngine Vector Field Repulsion..." << std::endl;

    combat::KitingConfig cfg;
    cfg.dangerRadius = 135.0f;
    cfg.kiteStepDistance = 35.0f;
    cfg.stepIntervalMs = 250;
    combat::KitingEngine kiter(cfg);

    TelemetryPacket packet{};
    packet.player.posX = 1000.0f;
    packet.player.posY = 1000.0f;
    packet.player.currentHP = 300;
    packet.player.maxHP = 300;

    // Giả lập 2 quái vật áp sát người chơi từ phía Đông (X > 1000)
    packet.entityCount = 2;
    packet.entities[0].id = 1;
    packet.entities[0].type = 1; // Monster
    packet.entities[0].posX = 1040.0f;
    packet.entities[0].posY = 1000.0f;
    packet.entities[0].currentHP = 100;
    packet.entities[0].maxHP = 100;
    packet.entities[0].extraFlags = 0;

    packet.entities[1].id = 2;
    packet.entities[1].type = 1; // Monster
    packet.entities[1].posX = 1050.0f;
    packet.entities[1].posY = 1010.0f;
    packet.entities[1].currentHP = 100;
    packet.entities[1].maxHP = 100;
    packet.entities[1].extraFlags = 0;

    navigation::TerrainGrid grid(6.0f);
    grid.Initialize(1000.0f, 1000.0f);

    navigation::Vec2 stepTarget;
    bool needKite = kiter.ComputeKiteStep(packet, grid, 1000, stepTarget);

    CHECK(needKite, "Quái áp sát trong tầm dangerRadius phải kích hoạt Kite Step");
    CHECK(stepTarget.x < 1000.0f, "Lực đẩy trường vector phải đẩy người chơi về phía ngược lại (Tây, X < 1000)");
    CHECK(stepTarget.Distance({1000.0f, 1000.0f}) >= 20.0f, "Khoảng lùi phải đáng kể để giữ khoảng cách");

    // Kiểm tra cooldown giữa 2 nhịp kiting
    navigation::Vec2 step2;
    bool cdBlocked = !kiter.ComputeKiteStep(packet, grid, 1100, step2);
    CHECK(cdBlocked, "Chưa hết stepIntervalMs (250ms) thì không được lùi tiếp (tránh spam rung chuột)");

    bool cdExpired = kiter.ComputeKiteStep(packet, grid, 1300, step2);
    CHECK(cdExpired, "Đã qua 300ms > stepIntervalMs thì được thực hiện nhịp lùi kế tiếp");

    // 2. Kiểm tra Tangent Sliding khi chạm biên tường địa hình (Wall Evacuation)
    {
        combat::KitingEngine wallKiter(cfg);
        navigation::TerrainGrid wallGrid(6.0f);
        wallGrid.Initialize(1000.0f, 1000.0f);

        // Tạo bức tường chắn hoàn toàn phía Tây (X <= 975)
        wallGrid.SetBlocked(970.0f, 1000.0f, 16.0f);
        wallGrid.SetBlocked(970.0f, 980.0f, 16.0f);
        wallGrid.SetBlocked(970.0f, 1020.0f, 16.0f);

        navigation::Vec2 wallStepTarget;
        bool wallKiteOk = wallKiter.ComputeKiteStep(packet, wallGrid, 2000, wallStepTarget);

        CHECK(wallKiteOk, "Kite step phai thanh cong khi gap tuong chan");
        CHECK(wallKiter.IsLastStepTangentSlide(), "INV-KITE-TANGENT: Phai kich hoat Tangent Sliding khi lui vao tuong chan");
        CHECK(wallGrid.IsWalkable(wallStepTarget.x, wallStepTarget.y), "Vi tri truot tiep tuyen phai nam tren dat di duoc");
        // Thay vì giật lùi vào tường Tây (X < 975 bị cản), nhân vật phải trượt theo trục Y (Bắc hoặc Nam)
        CHECK(std::abs(wallStepTarget.y - 1000.0f) >= 15.0f, "Phai truot tiep tuyen theo truc Y doc bo tuong (|dY| >= 15)");
        CHECK(wallStepTarget.x >= 985.0f, "Vi tri truot tiep tuyen khong duoc dam vao tuong Tay (X >= 985)");
        CHECK(wallKiter.State().totalTangentSlides >= 1, "State phai ghi nhan it nhat 1 lan Tangent Slide");
    }

    // 3. Kiểm tra Tangent Evacuation khi bị quái vật bao vây (> 8 quái trong bán kính 25u)
    {
        combat::KitingEngine surroundKiter(cfg);
        navigation::TerrainGrid openGrid(6.0f);
        openGrid.Initialize(1000.0f, 1000.0f);

        TelemetryPacket swarmPacket{};
        swarmPacket.player.posX = 1000.0f;
        swarmPacket.player.posY = 1000.0f;
        swarmPacket.player.currentHP = 300;
        swarmPacket.player.maxHP = 300;

        // Bố trí 10 quái vật bao vây trong bán kính 20u (< surroundRadius 25u),
        // tập trung chủ yếu từ hướng Nam và Đông Nam (Y > 1000), để hở sườn Bắc
        swarmPacket.entityCount = 10;
        for (uint32_t i = 0; i < 10; ++i) {
            float angle = 0.5f + static_cast<float>(i) * 0.25f; // góc quanh hướng Nam
            swarmPacket.entities[i].id = 100 + i;
            swarmPacket.entities[i].type = 1; // Monster
            swarmPacket.entities[i].posX = 1000.0f + std::cos(angle) * 20.0f;
            swarmPacket.entities[i].posY = 1000.0f + std::sin(angle) * 20.0f;
            swarmPacket.entities[i].currentHP = 100;
            swarmPacket.entities[i].maxHP = 100;
            swarmPacket.entities[i].extraFlags = 0;
        }

        navigation::Vec2 swarmStepTarget;
        bool swarmKiteOk = surroundKiter.ComputeKiteStep(swarmPacket, openGrid, 3000, swarmStepTarget);

        CHECK(swarmKiteOk, "Kite step phai thanh cong khi bi quai bao vay");
        CHECK(surroundKiter.IsLastStepTangentSlide(), "INV-KITE-SURROUND: Phai kich hoat Tangent Evacuation khi bi > 8 quai bao vay trong 25u");
        CHECK(openGrid.IsWalkable(swarmStepTarget.x, swarmStepTarget.y), "Vi tri thoat hiem tiep tuyen phai di duoc");
        CHECK(swarmStepTarget.Distance({1000.0f, 1000.0f}) >= 20.0f, "Khoang cach thoat hiem phai >= 20u");
    }

    // 4. Đo lường Định lượng Độ trễ Tính toán Hot Path 120Hz (< 5µs với N=32 quái)
    {
        combat::KitingEngine perfKiter(cfg);
        navigation::TerrainGrid perfGrid(6.0f);
        perfGrid.Initialize(1000.0f, 1000.0f);

        TelemetryPacket heavyPacket{};
        heavyPacket.player.posX = 1000.0f;
        heavyPacket.player.posY = 1000.0f;
        heavyPacket.player.currentHP = 300;
        heavyPacket.player.maxHP = 300;

        // Giả lập đúng 32 quái vật áp sát với các khoảng cách khác nhau
        heavyPacket.entityCount = 32;
        for (uint32_t i = 0; i < 32; ++i) {
            float angle = static_cast<float>(i) * (6.2831853f / 32.0f);
            float dist = 20.0f + static_cast<float>(i % 5) * 15.0f;
            heavyPacket.entities[i].id = 200 + i;
            heavyPacket.entities[i].type = 1;
            heavyPacket.entities[i].posX = 1000.0f + std::cos(angle) * dist;
            heavyPacket.entities[i].posY = 1000.0f + std::sin(angle) * dist;
            heavyPacket.entities[i].currentHP = 100;
            heavyPacket.entities[i].maxHP = 100;
            heavyPacket.entities[i].extraFlags = 0;
        }

        constexpr int kIterations = 1000;
        navigation::Vec2 dummyTarget;
        uint64_t simulatedTimeMs = 10000;

        const auto startPerf = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < kIterations; ++i) {
            simulatedTimeMs += 300; // Mỗi lần vượt qua stepIntervalMs để thực thi đầy đủ thuật toán
            perfKiter.ComputeKiteStep(heavyPacket, perfGrid, simulatedTimeMs, dummyTarget);
        }
        const auto endPerf = std::chrono::high_resolution_clock::now();

        const double totalNs = static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(endPerf - startPerf).count());
        const double avgUs = (totalNs / kIterations) / 1000.0;

        CHECK(avgUs < 5.0,
              "INV-ZERO-HEAP-HOTPATH: Thoi gian tinh toan Kiting Tangent phai < 5.0µs trong Hot Path 120Hz. Thuc te: " +
              std::to_string(avgUs) + " µs");
        std::cout << "     [Benchmark] KitingEngine Tangent Compute (N=32 threats): " << avgUs << " us/call" << std::endl;
    }

    std::cout << "  -> KitingEngine Repulsion, Tangent Sliding & Wall Evacuation OK" << std::endl;
}

// ==========================================================
// 25. TestMonkCombatCombo (Ice Strike, Tempest Bell & Double Dodge)
// ==========================================================
void TestMonkCombatCombo() {
    std::cout << "[Test 25] Monk Combat Combo - Ice Strike, Tempest Bell Boss Routine & Double Dodge..." << std::endl;
    KMBoxNet kmbox;
    kmbox.EnableEmulationMode(true);
    kmbox.SetIgnoreWindowFocus(true);

    ComboConfig cfg;
    cfg.isRangedMode = false;
    cfg.useMonkRotation = true;
    cfg.primarySkillVk = VK_LBUTTON;
    cfg.vaultSkillVk = 0x45;
    cfg.bellSkillVk = 0x51;
    cfg.dodgeRollVk = VK_SPACE;
    cfg.lifeFlaskVk = 0x31;
    cfg.comboIntervalMs = 100;
    cfg.bossStrikeGoal = 4;
    cfg.doubleDodgeEnabled = true;
    cfg.emergencyHpThreshold = 0.45f;
    cfg.emergencyBurstRatio = 0.20f;

    ReflexConfig rcfg;
    ReflexManager rm(rcfg);
    ComboManager cm(cfg);
    cm.SetReflexManager(&rm);

    TelemetryPacket packet{};
    packet.player.currentHP = 100;
    packet.player.maxHP = 100;
    packet.player.currentMana = 50;
    packet.player.maxMana = 100;
    packet.player.posX = 100.0f;
    packet.player.posY = 100.0f;

    packet.entityCount = 1;
    packet.entities[0].type = 1;
    packet.entities[0].extraFlags = 0;
    packet.entities[0].rarity = 0;
    packet.entities[0].posX = 110.0f;
    packet.entities[0].posY = 100.0f;

    bool normalMobOk = cm.Update(packet, kmbox, 1000) && (cm.State().totalCombosExecuted == 1) &&
                       (cm.State().bossIceStrikeCount == 0) && (cm.State().doubleDodgeStep == 0);
    CHECK(normalMobOk, "Normal mob Monk LMB attack failed");

    packet.entities[0].extraFlags = 1;
    packet.entities[0].rarity = 3;

    bool strikesOk = true;
    for (int s = 1; s <= 4; ++s) {
        if (!cm.Update(packet, kmbox, 1000 + s * 200) || cm.State().bossIceStrikeCount != s) {
            strikesOk = false;
        }
    }
    CHECK(strikesOk, "Boss Ice Strikes 1..4 failed");

    bool bellPlaced = cm.Update(packet, kmbox, 2000) && (cm.State().totalBellsPlaced == 1) && (cm.State().bossIceStrikeCount == 0);
    bool nextStrike = cm.Update(packet, kmbox, 2200) && (cm.State().bossIceStrikeCount == 1);
    CHECK(bellPlaced && nextStrike, "Boss Tempest Bell placement & next cycle failed");

    packet.player.currentHP = 40;
    bool ddStep1 = cm.Update(packet, kmbox, 2500) && (cm.State().doubleDodgeStep == 1);
    bool ddStep2 = cm.Update(packet, kmbox, 2660) && (cm.State().doubleDodgeStep == 2);
    bool ddStep3 = cm.Update(packet, kmbox, 2870) && (cm.State().doubleDodgeStep == 3);
    cm.Update(packet, kmbox, 3040);
    bool ddDone = (cm.State().doubleDodgeStep == 0) && (cm.State().totalDoubleDodges == 1);
    CHECK(ddStep1 && ddStep2 && ddStep3 && ddDone, "Double dodge 3-step sequence failed");

    std::cout << "  -> Monk Ice Strike, Boss 4-Strike + Tempest Bell, and Double Dodge (E->Space->Flask) OK" << std::endl;
}

// ==========================================================
// 38. Ranged Combat RMB, Phản Xạ Né Phím E & Combat Stop Distance (380)
// ==========================================================
void TestRangedCombatAndKiting() {
    std::cout << "[Test 38] Ranged Combat RMB, Pháº£n Xáº¡ NÃ© PhÃ­m E & QuestNav 380 Distance..." << std::endl;
    KMBoxNet kmbox;
    kmbox.EnableEmulationMode(true);
    kmbox.SetIgnoreWindowFocus(true);

    // 1. Kiá»ƒm tra ComboManager á»Ÿ cháº¿ Ä‘á»™ ÄÃ¡nh Xa máº·c Ä‘á»‹nh (RMB + isRangedMode)
    ComboConfig cfg; // Sá»­ dá»¥ng cáº¥u hÃ¬nh máº·c Ä‘á»‹nh (primarySkillVk = VK_RBUTTON, isRangedMode = true)
    CHECK(cfg.primarySkillVk == VK_RBUTTON, "primarySkillVk máº·c Ä‘á»‹nh pháº£i lÃ  Chuá»™t Pháº£i (VK_RBUTTON)");
    CHECK(cfg.isRangedMode == true, "isRangedMode máº·c Ä‘á»‹nh pháº£i lÃ  true");
    CHECK(cfg.rangedEngageDist == 450.0f, "rangedEngageDist máº·c Ä‘á»‹nh pháº£i lÃ  450.0f");
    CHECK(cfg.kitingDangerDist == 120.0f, "kitingDangerDist máº·c Ä‘á»‹nh pháº£i lÃ  120.0f");
    CHECK(cfg.vaultCooldownMs == 1200, "vaultCooldownMs máº·c Ä‘á»‹nh pháº£i lÃ  1200");
    CHECK(cfg.vaultSkillVk == 0x45, "vaultSkillVk máº·c Ä‘á»‹nh pháº£i lÃ  phÃ­m 'E' (0x45)");

    ComboManager cm(cfg);

    TelemetryPacket packet{};
    packet.player.currentHP = 100;
    packet.player.maxHP = 100;
    packet.player.currentMana = 50;
    packet.player.maxMana = 100;
    packet.player.posX = 1500.0f;
    packet.player.posY = 2500.0f;
    packet.player.posZ = -120.0f;

    // A. QuÃ¡i váº­t á»Ÿ cá»± ly táº§m xa an toÃ n (dist = 250 units: 120 < 250 <= 450)
    // -> Báº¯n Chuá»™t Pháº£i (RMB), KHÃ”NG nÃ© E
    packet.entityCount = 1;
    packet.entities[0].type = 1; // Monster
    packet.entities[0].extraFlags = 0; // Alive
    packet.entities[0].rarity = 0;
    packet.entities[0].posX = 1500.0f + 250.0f;
    packet.entities[0].posY = 2500.0f;

    CHECK(cm.Update(packet, kmbox, 1000), "Pháº£i xáº£ ká»¹ nÄƒng Chuá»™t Pháº£i [RMB] vÃ o quÃ¡i á»Ÿ cá»± ly xa (250)");
    CHECK(cm.State().totalCombosExecuted == 1, "Tá»•ng combo thá»±c thi pháº£i lÃ  1");
    CHECK(cm.State().lastVaultMs == 0, "KhÃ´ng Ä‘Æ°á»£c nÃ© phÃ­m E khi quÃ¡i á»Ÿ cá»± ly an toÃ n");

    // B. Boss Encounter á»Ÿ cá»± ly xa (dist = 350 <= 450):
    // Trong Ranged mode, Ä‘Ã¡nh Boss báº±ng Chuá»™t Pháº£i [RMB], khÃ´ng dÃ¹ng rotation cáº­n chiáº¿n Monk
    packet.entities[0].extraFlags = 1; // Boss
    packet.entities[0].rarity = 3;
    packet.entities[0].posX = 1500.0f + 350.0f;
    packet.entities[0].posY = 2500.0f;

    CHECK(cm.Update(packet, kmbox, 1300), "Pháº£i xáº£ ká»¹ nÄƒng Chuá»™t Pháº£i [RMB] vÃ o Boss");
    CHECK(cm.State().totalCombosExecuted == 2, "Tá»•ng combo thá»±c thi pháº£i tÄƒng lÃªn 2");
    CHECK(cm.State().lastVaultMs == 0, "ÄÃ¡nh Boss tá»« xa khÃ´ng kÃ­ch hoáº¡t nÃ© E");

    // C. QuÃ¡i Ã¡p sÃ¡t nguy hiá»ƒm: cá»± ly 80 units (dist <= kitingDangerDist 120.0f)
    // -> KÃ­ch hoáº¡t ngay phÃ­m nÃ© thoÃ¡t hiá»ƒm 'E' (Frozen Locus / Dash)
    packet.entities[0].extraFlags = 0;
    packet.entities[0].rarity = 0;
    packet.entities[0].posX = 1500.0f + 80.0f;
    packet.entities[0].posY = 2500.0f;

    CHECK(cm.Update(packet, kmbox, 2000), "Pháº£i kÃ­ch hoáº¡t nÃ© phÃ­m E khi quÃ¡i Ã¡p sÃ¡t cá»± ly 80 (<= 120)");
    CHECK(cm.State().lastVaultMs == 2000, "lastVaultMs pháº£i ghi nháº­n má»‘c thá»i gian 2000ms");

    // D. Cooldown cá»§a phÃ­m nÃ© 'E' (vaultCooldownMs = 1200ms):
    // Sau 400ms (nowMs = 2400 < 2000 + 1200), quÃ¡i váº«n á»Ÿ cá»± ly 80:
    // KHÃ”NG Ä‘Æ°á»£c spam phÃ­m E vÃ¬ Ä‘ang cooldown!
    CHECK(cm.Update(packet, kmbox, 2400), "Pháº£i tiáº¿p tá»¥c xáº£ RMB khi Ä‘ang cooldown nÃ© E");
    CHECK(cm.State().lastVaultMs == 2000, "lastVaultMs khÃ´ng Ä‘Æ°á»£c thay Ä‘á»•i trong thá»i gian cooldown 1200ms");

    // Sau khi cooldown káº¿t thÃºc (nowMs = 3300 >= 2000 + 1200):
    // QuÃ¡i váº«n á»Ÿ cá»± ly nguy hiá»ƒm 80 -> KÃ­ch hoáº¡t nÃ© phÃ­m E láº§n 2!
    CHECK(cm.Update(packet, kmbox, 3300), "Pháº£i kÃ­ch hoáº¡t nÃ© phÃ­m E láº§n 2 khi cooldown 1200ms Ä‘Ã£ háº¿t");
    CHECK(cm.State().lastVaultMs == 3300, "lastVaultMs pháº£i cáº­p nháº­t thÃ nh 3300ms");

    // E. Pháº£n xáº¡ nÃ© E khi bá»‹ sÃ¡t thÆ°Æ¡ng (takingDamage):
    // QuÃ¡i á»Ÿ xa 280 units (> 120), nhÆ°ng nhÃ¢n váº­t bá»‹ trá»« mÃ¡u tá»« 100 xuá»‘ng 80
    // Cooldown tiáº¿p theo: 3300 + 1200 = 4500. Tick táº¡i 4600ms:
    packet.entities[0].posX = 1500.0f + 280.0f;
    packet.entities[0].posY = 2500.0f;
    packet.player.currentHP = 80; // Máº¥t 20 HP
    CHECK(cm.Update(packet, kmbox, 4600), "Pháº£i kÃ­ch hoáº¡t nÃ© E khi nháº­n sÃ¡t thÆ°Æ¡ng");
    CHECK(cm.State().lastVaultMs == 4600, "lastVaultMs pháº£i cáº­p nháº­t thÃ nh 4600ms sau khi bá»‹ Ä‘Ã¡nh");

    // 2. Kiá»ƒm tra QuestNavigator vá»›i cá»± ly chiáº¿n Ä‘áº¥u Ä‘Ã¡nh xa combatStopDistance = 380.0f
    QuestNavConfig qcfg;
    CHECK(qcfg.combatStopDistance == 380.0f, "combatStopDistance máº·c Ä‘á»‹nh pháº£i lÃ  380.0f");
    CHECK(qcfg.isRanged == true, "isRanged máº·c Ä‘á»‹nh pháº£i lÃ  true");

    qcfg.enabled = true;
    qcfg.autoPatrol = true;
    qcfg.scenario = MapScenario::FULL_CLEAR;
    QuestNavigator qn(qcfg);

    // Reset HP & vá»‹ trÃ­ ngÆ°á»i chÆ¡i
    packet.player.currentHP = 100;
    packet.player.posX = 1500.0f;
    packet.player.posY = 2500.0f;
    packet.player.posZ = -120.0f;

    // Case 1: QuÃ¡i thÆ°á»ng á»Ÿ cá»± ly 350 (<= 380):
    // QuestNavigator pháº£i Dá»ªNG tuáº§n tra (return false, isPatrolling = false)
    // Ä‘á»ƒ nhÆ°á»ng chuá»™t cho ComboManager xáº£ Ä‘áº¡n táº§m xa!
    packet.entityCount = 1;
    packet.entities[0].id = 1;
    packet.entities[0].type = 1;
    packet.entities[0].extraFlags = 0;
    packet.entities[0].currentHP = 1000;
    packet.entities[0].maxHP = 1000;
    packet.entities[0].posX = 1500.0f + 350.0f;
    packet.entities[0].posY = 2500.0f;

    CHECK(!qn.Update(packet, kmbox, 5000), "QuestNavigator pháº£i dá»«ng tuáº§n tra khi cÃ³ quÃ¡i trong táº§m chiáº¿n Ä‘áº¥u 350 (<= 380)");
    CHECK(!qn.State().isPatrolling, "isPatrolling pháº£i chuyá»ƒn thÃ nh false khi cÃ³ quÃ¡i trong cá»± ly 380");

    // Case 2: QuÃ¡i thÆ°á»ng á»Ÿ cá»± ly 410 (> 380):
    // QuÃ¡i náº±m ngoÃ i táº§m combat -> QuestNavigator tiáº¿p tá»¥c tuáº§n tra (return true, isPatrolling = true)
    packet.entities[0].posX = 1500.0f + 410.0f;
    packet.entities[0].posY = 2500.0f;
    CHECK(qn.Update(packet, kmbox, 6000), "QuestNavigator pháº£i tiáº¿p tá»¥c tuáº§n tra khi quÃ¡i á»Ÿ ngoÃ i cá»± ly 380");
    CHECK(qn.State().isPatrolling, "isPatrolling pháº£i lÃ  true khi quÃ¡i ngoÃ i táº§m 380");

    // Case 3: Boss á»Ÿ cá»± ly 360 (<= 380):
    // ÄÃ£ vÃ o táº§m báº¯n Ä‘Ã¡nh xa -> Dá»«ng tiáº¿p cáº­n Boss (return false) Ä‘á»ƒ ComboManager báº¯n RMB
    QuestNavigator qnBoss(qcfg);
    packet.entities[0].id = 2;
    packet.entities[0].extraFlags = 1; // Boss
    packet.entities[0].rarity = 3;
    packet.entities[0].currentHP = 50000;
    packet.entities[0].maxHP = 50000;
    packet.entities[0].posX = 1500.0f + 360.0f;
    packet.entities[0].posY = 2500.0f;

    CHECK(!qnBoss.Update(packet, kmbox, 7000), "QuestNavigator pháº£i dá»«ng tiáº¿p cáº­n Boss khi Boss trong cá»± ly 360 (<= 380)");

    // Case 4: Boss á»Ÿ cá»± ly 460 (> 380):
    // Boss á»Ÿ xa táº§m báº¯n -> Di chuyá»ƒn tiáº¿p cáº­n Boss báº±ng A* (return true)
    packet.entities[0].posX = 1500.0f + 460.0f;
    packet.entities[0].posY = 2500.0f;
    CHECK(qnBoss.Update(packet, kmbox, 7800), "QuestNavigator pháº£i tiáº¿p cáº­n Boss khi Boss á»Ÿ xa hÆ¡n 380 Ä‘Æ¡n vá»‹");

    std::cout << "  -> Ranged Combat RMB, Pháº£n Xáº¡ NÃ© PhÃ­m E & QuestNav Combat Stop 380 Distance OK" << std::endl;
}

// ==========================================================
// 39. Post-Death Respawn & Boss Arena Transition Invariants
// ==========================================================
void TestPostDeathRespawnAndArenaTransition() {
    std::cout << "[Test 39] Post-Death Respawn & Boss Arena Transition..." << std::endl;

    ReflexConfig rcfg{};
    rcfg.autoRespawn = true;
    rcfg.respawnDelayMs = 2200;
    ReflexManager rm(rcfg);

    GameSession sess;
    sess.SetPlayerAddress(0x10001000);
    sess.SetPositionAddress(0x10002000);
    sess.SetShieldAddress(0x10003000);
    sess.SetManaAddress(0x10004000);
    sess.SetSpiritAddress(0x10005000);
    rm.SetGameSession(&sess);

    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);
    TelemetryPacket pkt{};
    pkt.player.maxHP = 1200;
    pkt.player.currentHP = 0;

    rm.Update(pkt, kmbox, 1000);
    CHECK(rm.State().deathTimestampMs == 1000 && sess.PlayerAddress() == 0x10001000, "Death timestamp or pre-respawn session mismatch");

    pkt.player.currentHP = 1200;
    rm.Update(pkt, kmbox, 3500);

    bool pointersResetOk = (rm.State().deathTimestampMs == 0) &&
                           (sess.PlayerAddress() == 0) &&
                           (sess.PositionAddress() == 0) &&
                           (sess.ShieldAddress() == 0) &&
                           (sess.ManaAddress() == 0) &&
                           (sess.SpiritAddress() == 0) &&
                           (sess.AutoScanState() == GameSession::AutoPositionState::CAPTURING_STILL_BASELINE);
    CHECK(pointersResetOk, "Post-death respawn pointers reset mismatch");

    GameSession arenaSess;
    std::string initStatus;
    arenaSess.Initialize("sim", initStatus);
    arenaSess.SetExpectedMaxHP(500);
    arenaSess.SetExpectedMaxMana(300);
    arenaSess.SetExpectedMaxES(200);
    arenaSess.SetExpectedMaxSpirit(100);

    arenaSess.OnZoneSceneChanged("The Riverbank");
    arenaSess.OnZoneLoadingFinished("The Riverbank");
    arenaSess.SetPlayerAddress(0xABC000);
    arenaSess.SetPositionAddress(0xDEF000);

    arenaSess.OnZoneSceneChanged("The Riverbank");
    bool ignoreSameZoneOk = (arenaSess.PlayerAddress() == 0xABC000) && (arenaSess.PositionAddress() == 0xDEF000);

    arenaSess.OnInstanceTransition();
    arenaSess.OnZoneSceneChanged("The Riverbank");
    bool bossArenaResetOk = !arenaSess.IsInstanceTransitionPending() &&
                            (arenaSess.PlayerAddress() == 0) &&
                            (arenaSess.PositionAddress() == 0) &&
                            (arenaSess.AutoScanState() == GameSession::AutoPositionState::WAITING_ZONE_LOAD);
    CHECK(ignoreSameZoneOk && bossArenaResetOk, "Boss Arena sub-instance transition failed");

    std::cout << "  -> Post-Death Respawn & Boss Arena Transition OK" << std::endl;
}

void TestCIChargedStaffRotationAndSurvival() {
    std::cout << "[Test 46] CI Charged Staff Rotation & ES Recovery Invariants..." << std::endl;

    KMBoxNet kmbox;
    kmbox.EnableEmulationMode(true);

    ReflexConfig reflexCfg;
    reflexCfg.flaskEnabled = false; // CI khÃ´ng dÃ¹ng bÃ¬nh mÃ¡u
    reflexCfg.ciEvasionEnabled = true;
    reflexCfg.ciDodgeEsThreshold = 0.75f;    // < 75% -> nÃ© Ä‘Ã²n
    reflexCfg.ciReengageEsThreshold = 0.90f; // >= 90% -> tÃ¡i giao chiáº¿n
    reflexCfg.ciManaFlaskThreshold = 0.50f;
    reflexCfg.humanReactionEnabled = false; // Tá»©c thá»i cho unit test
    ReflexManager reflexMgr(reflexCfg);

    ComboConfig comboCfg;
    comboCfg.isCiChargedStaffMode = true;
    comboCfg.isRangedMode = false;
    comboCfg.staffBuffSkillVk = VK_RBUTTON;
    comboCfg.staffMeleeSkillVk = VK_LBUTTON;
    comboCfg.builderSkillVk = 0x51; // Q
    comboCfg.finisherRSkillVk = 0x52; // R
    comboCfg.chargedStaffBuffDurationMs = 40000;
    comboCfg.requiredChargesForBuff = 4;
    comboCfg.requiredComboForR = 4;
    comboCfg.comboIntervalMs = 50; // GiÃ£n cÃ¡ch ngáº¯n cho unit test
    ComboManager comboMgr(comboCfg);
    comboMgr.SetReflexManager(&reflexMgr);

    uint64_t nowMs = 10000;
    TelemetryPacket packet{};
    packet.player.maxHP = 1;
    packet.player.currentHP = 1;
    packet.player.maxES = 3000;
    packet.player.currentES = 3000; // 100% ES
    packet.player.maxMana = 500;
    packet.player.currentMana = 400;
    packet.player.posX = 100.0f;
    packet.player.posY = 100.0f;

    // 1 Monster trong táº§m Ä‘Ã¡nh
    packet.entityCount = 1;
    packet.entities[0].id = 1;
    packet.entities[0].type = 1; // Monster
    packet.entities[0].posX = 150.0f;
    packet.entities[0].posY = 100.0f;
    packet.entities[0].distanceToPlayer = 50.0f;
    packet.entities[0].currentHP = 10000;
    packet.entities[0].maxHP = 10000;

    // Phase 1: ES Ä‘áº§y (100% >= 90%) -> ChÆ°a vÃ o recovery mode
    CHECK(reflexMgr.Update(packet, kmbox, nowMs) == false, "Reflex Update khÃ´ng lá»—i");
    CHECK(!reflexMgr.IsCiRecoveryMode(), "Ban Ä‘áº§u ES 100% khÃ´ng á»Ÿ trong Recovery Mode");

    // Phase 2: Sá»¥t giáº£m ES xuá»‘ng < 75% (vÃ­ dá»¥ 2100/3000 = 70%)
    nowMs += 100;
    packet.player.currentES = 2100;
    CHECK(reflexMgr.Update(packet, kmbox, nowMs) == false, "Reflex Update thÃ nh cÃ´ng khi ES tá»¥t");
    CHECK(reflexMgr.IsCiRecoveryMode(), "ES < 75% pháº£i kÃ­ch hoáº¡t isCiRecoveryMode = true!");
    CHECK(reflexMgr.State().dodgeCount >= 1, "Pháº£i kÃ­ch hoáº¡t nÃ© Ä‘Ã²n (Space) khi ES < 75%!");

    // Phase 3: Trong khi isCiRecoveryMode == true vÃ  ES chÆ°a Ä‘áº¡t 90% (vÃ­ dá»¥ 2500/3000 = 83.3%)
    nowMs += 100;
    packet.player.currentES = 2500;
    reflexMgr.Update(packet, kmbox, nowMs);
    CHECK(reflexMgr.IsCiRecoveryMode(), "ES 83.3% (< 90%) váº«n pháº£i DUY TRÃŒ Recovery Mode!");
    CHECK(comboMgr.Update(packet, kmbox, nowMs) == false, "ComboManager PHáº¢I Tá»ª CHá»I Táº¤N CÃ”NG khi Ä‘ang á»Ÿ Recovery Mode!");

    // Phase 4: ES há»“i phá»¥c trÃªn 90% (2750/3000 = 91.6%) -> ThoÃ¡t Recovery Mode
    nowMs += 100;
    packet.player.currentES = 2750;
    reflexMgr.Update(packet, kmbox, nowMs);
    CHECK(!reflexMgr.IsCiRecoveryMode(), "ES >= 90% pháº£i giáº£i phÃ³ng isCiRecoveryMode vá» false!");

    // Phase 5: Rotation CI Combo - TÃ­ch 4 Ä‘iá»ƒm Combo qua chiÃªu Q rá»“i tung chiÃªu R
    // ÄÃ²n Q #1
    nowMs += 100;
    CHECK(comboMgr.Update(packet, kmbox, nowMs) == true, "Combo Update tung chiÃªu Q #1");
    CHECK(comboMgr.GetComboPoints() == 1, "Combo points pháº£i lÃ  1 sau láº§n cast Q Ä‘áº§u tiÃªn");
    CHECK(comboMgr.GetRage() == 10, "Rage pháº£i tÄƒng lÃªn 10");

    // ÄÃ²n Q #2
    nowMs += 100;
    CHECK(comboMgr.Update(packet, kmbox, nowMs) == true, "Combo Update tung chiÃªu Q #2");
    CHECK(comboMgr.GetComboPoints() == 2, "Combo points pháº£i lÃ  2");

    // ÄÃ²n Q #3
    nowMs += 100;
    CHECK(comboMgr.Update(packet, kmbox, nowMs) == true, "Combo Update tung chiÃªu Q #3");
    CHECK(comboMgr.GetComboPoints() == 3, "Combo points pháº£i lÃ  3");

    // ÄÃ²n Q #4
    nowMs += 100;
    CHECK(comboMgr.Update(packet, kmbox, nowMs) == true, "Combo Update tung chiÃªu Q #4");
    CHECK(comboMgr.GetComboPoints() == 4, "Combo points pháº£i lÃ  4");

    // ÄÃ²n káº¿t liá»…u R khi Ä‘Ã£ cÃ³ 4 combo points
    nowMs += 100;
    CHECK(comboMgr.Update(packet, kmbox, nowMs) == true, "Combo Update pháº£i tung Ä‘Ã²n káº¿t liá»…u R");
    CHECK(comboMgr.GetComboPoints() == 0, "Combo points pháº£i Ä‘Æ°á»£c reset vá» 0 sau chiÃªu R");

    // Phase 6: Quáº£n lÃ½ Charges & Buff 40s Charged Staff (RMB)
    // TÃ­ch Ä‘á»§ 4 charges
    comboMgr.SetCharges(4);
    CHECK(comboMgr.GetCharges() == 4, "Set 4 charges thÃ nh cÃ´ng");
    CHECK(!comboMgr.IsChargedStaffBuffActive(nowMs), "Ban Ä‘áº§u buff Charged Staff chÆ°a active");

    nowMs += 100;
    CHECK(comboMgr.Update(packet, kmbox, nowMs) == true, "CÃ³ 4 charges -> Pháº£i tung chiÃªu RMB [Charged Staff]");
    CHECK(comboMgr.IsChargedStaffBuffActive(nowMs), "Sau khi cast RMB, buff 40s pháº£i ACTIVE");
    CHECK(comboMgr.GetCharges() == 0, "Charges pháº£i Ä‘Æ°á»£c tiÃªu hao khi kÃ­ch hoáº¡t Charged Staff");

    // Thá»­ cast tiáº¿p trong chu ká»³ 40s (vÃ­ dá»¥ 10 giÃ¢y sau) -> Buff cÃ²n 30s, cáº¥m spam láº¡i RMB!
    nowMs += 10000;
    comboMgr.SetCharges(5); // CÃ³ láº¡i 5 charges
    CHECK(comboMgr.IsChargedStaffBuffActive(nowMs), "Sau 10s buff váº«n cÃ²n active");
    comboMgr.Update(packet, kmbox, nowMs);
    CHECK(comboMgr.GetCharges() == 5, "Charges KHÃ”NG Bá»Š TIÃŠU HAO vÃ¬ buff Charged Staff váº«n cÃ²n hiá»‡u lá»±c");

    // Phase 7: Tá»‘i Æ°u hÃ³a Mana cho CI build
    packet.player.currentMana = 200; // 200/500 = 40% <= 50%
    const uint32_t prevManaUses = reflexMgr.State().manaUses;
    nowMs += 100;
    reflexMgr.Update(packet, kmbox, nowMs);
    CHECK(reflexMgr.State().manaUses == prevManaUses + 1, "CI build vá»›i Mana <= 50% pháº£i tá»± Ä‘á»™ng uá»‘ng Mana Flask");

    std::cout << "  -> CI Charged Staff Rotation, ES Recovery & Mana Optimization OK" << std::endl;
}

// ==========================================================
// 71. Test SkillsToml Table-Driven Combat & UnstuckHandler Invariants (Doc 29 P4)
//  - Kiểm tra nạp và parse data/combat/skills.toml.
//  - Kiểm tra tuyển chọn kỹ năng (Attack vs CombatBuff, khoảng cách, cooldown, rarity, vitals).
//  - Tích hợp SkillsTable vào ComboManager (chế độ table-driven).
//  - Bất biến UNSTUCK (8000): Ưu tiên cao hơn Combat (7000), AreaEvent (6500), Pickup (6000), Explore (4000).
//  - Kiểm tra trigger kẹt di chuyển (MovementStuck: XYZ đứng yên > 10000ms trong Explore/Combat/Pickup).
//  - Kiểm tra trigger hành động lặp (RepetitiveAction: lặp cùng action >= 200 ticks ở map, >= 30 ticks ở town).
//  - Kiểm tra blacklist nhặt đồ (PickupFailure: hỏng >= 5 lần trên cùng ID vật phẩm).
//  - Kiểm tra quy trình phục hồi (Execute step, dodge roll, giải phóng kẹt khi di chuyển > 15u).
//  - Kiểm tra BotBrain Arbiter: UNSTUCK (8000) THẮNG COMBAT (7000) và EXPLORE (4000).
// ==========================================================
void TestSkillsTomlAndUnstuckHandlerInvariants() {
    std::cout << "[Test 71] SkillsToml & UnstuckHandler Invariants (Doc 29 P4)..." << std::endl;

    // 1. Kiểm tra nạp và parse skills.toml
    SkillsTable skills;
    CHECK(skills.LoadSkills("data/combat/skills.toml") && skills.Skills().size() >= 4,
          "Phai nap va parse thanh cong data/combat/skills.toml (>= 4 skills)");

    const auto& sList = skills.Skills();
    bool s0Ok = (sList[0].name == "Charged Staff" && sList[0].kind == SkillKind::CombatBuff && sList[0].vkCode == VK_RBUTTON && sList[0].priority == 1 && sList[0].maintain);
    bool s1Ok = (sList[1].name == "Whirling Assault" && sList[1].kind == SkillKind::Attack && (sList[1].vkCode == 0x46 || sList[1].vkCode == VK_LBUTTON) && sList[1].priority == 2 && sList[1].maxDistance == 180.0f);
    bool s2Ok = (sList[2].name == "Rage Builder" && sList[2].kind == SkillKind::Attack && sList[2].vkCode == 0x51 && sList[2].priority == 3 && sList[2].cooldownMs == 300);
    bool s3Ok = (sList[3].name == "Stagger Finisher" && sList[3].kind == SkillKind::Attack && sList[3].vkCode == 0x52 && sList[3].priority == 4 && sList[3].targetRarityMin == 2);
    CHECK(s0Ok && s1Ok && s2Ok && s3Ok, "Thong so 4 skills co ban phai day du chinh xac");

    // 2. Kiểm tra tuyển chọn kỹ năng (SelectBestSkill)
    SkillDefinition* sel1 = skills.SelectBestSkill(1000, 100.0f, 0, 1.0f, 0.5f, 1.0f, SkillKind::Attack);
    SkillDefinition* sel2 = skills.SelectBestSkill(1000, 220.0f, 0, 1.0f, 0.5f, 1.0f, SkillKind::Attack);
    SkillDefinition* selNoMana = skills.SelectBestSkill(1000, 100.0f, 0, 1.0f, 0.01f, 1.0f, SkillKind::Attack);
    SkillDefinition* selBuff = skills.SelectBestSkill(1000, 0.0f, 0, 1.0f, 0.5f, 1.0f, SkillKind::CombatBuff);
    CHECK(sel1 && sel1->name == "Whirling Assault" && sel2 && sel2->name == "Rage Builder" &&
          selNoMana == nullptr && selBuff && selBuff->name == "Charged Staff",
          "Tuyen chon skill (can, tam trung, thieu mana fail-closed, combat buff) phai dung");

    SkillDefinition closeSkill{};
    closeSkill.kind = SkillKind::Attack;
    closeSkill.maxDistance = 250.0f;
    closeSkill.closeTargets = 3;
    CHECK(!closeSkill.IsEligible(1000, 40.0f, 0, 1.0f, 1.0f, 1.0f, 2) &&
          closeSkill.IsEligible(1000, 40.0f, 0, 1.0f, 1.0f, 1.0f, 3),
          "close_targets phai fail-closed khi nearbyEnemies thieu");

    // 3. Tích hợp SkillsTable vào ComboManager
    ComboManager comboMgr;
    CHECK(comboMgr.LoadSkills("data/combat/skills.toml") && comboMgr.Skills().Skills().size() >= 4,
          "ComboManager phai tich hop dung SkillsTable tu skills.toml");

    // 4. Kiểm tra UnstuckHandler
    UnstuckConfig uCfg;
    uCfg.failMovementCheckIntervalMs = 10000;
    uCfg.maxRepetitiveMap = 200;
    uCfg.maxRepetitiveTown = 30;
    uCfg.maxPickupFails = 5;
    UnstuckHandler unstuck(uCfg);

    TelemetryPacket pkt{};
    pkt.player.posX = 100.0f;
    pkt.player.posY = 100.0f;
    pkt.player.currentHP = 1000;
    pkt.player.maxHP = 1000;

    CHECK(!unstuck.IsStuck() && unstuck.Propose(pkt, 1000).priority == 0,
          "Khoi dau UnstuckHandler phai binh thuong (priority=0)");

    // 4a. Repetitive action trigger
    for (int i = 0; i < 200; ++i) {
        unstuck.RecordAction(BotActionKind::Explore, 10, 1000 + i * 10, false);
    }
    ActionProposal pRep = unstuck.Propose(pkt, 5000);
    CHECK(unstuck.IsStuck() && unstuck.Reason() == UnstuckReason::RepetitiveAction &&
          pRep.kind == BotActionKind::Unstuck && pRep.priority == bot_priority::UNSTUCK,
          "200 repetitive actions phai trigger Unstuck 8000");

    // 4b. Clear unstuck on movement > 15u
    TelemetryPacket movedPkt = pkt;
    movedPkt.player.posX = 120.0f;
    CHECK(unstuck.Propose(movedPkt, 5100).priority == 0 && !unstuck.IsStuck(),
          "Di chuyen xa > 15u phai giai phong ket");

    // 4c. Movement stuck (idle > 10000ms)
    unstuck.Reset();
    unstuck.RecordAction(BotActionKind::Explore, 0, 10000, false);
    unstuck.Propose(pkt, 10000);
    ActionProposal p5s = unstuck.Propose(pkt, 15000);
    bool notStuckAt5s = !unstuck.IsStuck() && (p5s.priority == 0);
    ActionProposal p10s = unstuck.Propose(pkt, 20500);
    bool stuckAt10s = unstuck.IsStuck() && (p10s.priority == bot_priority::UNSTUCK) &&
                      (unstuck.Reason() == UnstuckReason::MovementStuck);
    CHECK(notStuckAt5s && stuckAt10s, "MovementStuck trigger dung sau 10000ms");

    // 4d. Pickup failure blacklist
    unstuck.Reset();
    const uint32_t stuckItemId = 999;
    for (int i = 0; i < 4; ++i) { unstuck.RecordPickupFailure(stuckItemId); }
    bool notBlacklistedYet = !unstuck.IsItemBlacklisted(stuckItemId);
    unstuck.RecordPickupFailure(stuckItemId);
    CHECK(notBlacklistedYet && unstuck.IsItemBlacklisted(stuckItemId) && unstuck.IsStuck() &&
          unstuck.Reason() == UnstuckReason::PickupFailure,
          "5 lan pickup failure phai blacklist va trigger Unstuck");

    unstuck.Reset();
    unstuck.ManualTriggerStuck(UnstuckReason::MovementStuck, 29900);
    KMBoxNet kmbox;
    kmbox.EnableEmulationMode(true);
    kmbox.SetIgnoreWindowFocus(true);
    ActionProposal pExec{};
    pExec.kind = BotActionKind::Unstuck;
    pExec.priority = bot_priority::UNSTUCK;
    CHECK(unstuck.Execute(pExec, pkt, kmbox, 30000) && unstuck.RecoveryAttempts() == 1,
          "Execute Unstuck lan 1 phai KeyDown WASD va dem attempt=1");
    CHECK(unstuck.Execute(pExec, pkt, kmbox, 30360) && unstuck.RecoveryAttempts() == 1,
          "Sau 350ms phai nha WASD, chua tang attempt");
    CHECK(unstuck.Execute(pExec, pkt, kmbox, 30400) && unstuck.RecoveryAttempts() == 2,
          "Lan 2 phai dodge roll va dem attempt=2");

    // 5. Arbiter integration: UNSTUCK (8000) > COMBAT (7000)
    BotBrain arbiter;
    arbiter.AddHandler(&unstuck);
    arbiter.AddHandler(&comboMgr);
    QuestNavConfig qCfg;
    qCfg.enabled = true;
    QuestNavigator qNav(qCfg);
    arbiter.AddHandler(&qNav);

    TelemetryPacket dangerPkt = pkt;
    dangerPkt.entityCount = 1;
    dangerPkt.entities[0].id = 1;
    dangerPkt.entities[0].type = 1;
    dangerPkt.entities[0].currentHP = 500;
    dangerPkt.entities[0].maxHP = 500;
    dangerPkt.entities[0].posX = 120.0f;
    dangerPkt.entities[0].posY = 100.0f;

    unstuck.ManualTriggerStuck(UnstuckReason::MovementStuck, 40000);
    CHECK(arbiter.Process(dangerPkt, kmbox, 40000) && arbiter.LastChosen().kind == BotActionKind::Unstuck,
          "UNSTUCK (8000) phai thang COMBAT (7000) va EXPLORE (4000)");

    // 4f. Bất biến INV-UNSTUCK-EXPLORE-EXEMPTION: entityId == 0 (Explore tuần tra) không bao giờ bị RepetitiveAction
    unstuck.Reset();
    for (int i = 0; i < 2000; ++i) {
        unstuck.RecordAction(BotActionKind::Explore, 0, 50000 + i * 10, false);
    }
    CHECK(!unstuck.IsStuck() && unstuck.Propose(pkt, 70000).priority == 0,
          "Explore entityId=0 lap 2000 ticks tuyet doi khong trigger RepetitiveAction (INV-UNSTUCK-EXPLORE-EXEMPTION)");

    // 4g. Bất biến INV-INPUT-ACTION-RATE-LIMIT: Action Gatekeeper giới hạn trần actions/giây
    KMBoxNet gatekeeperKm;
    gatekeeperKm.SetIgnoreWindowFocus(true);
    gatekeeperKm.EnableEmulationMode(true);
    gatekeeperKm.SetMaxActionsPerSecond(10);
    gatekeeperKm.ClearActionHistory();
    for (int i = 0; i < 10; ++i) {
        CHECK(gatekeeperKm.CanPerformAction(false), "Action thu " + std::to_string(i + 1) + " phai duoc chap thuan");
        gatekeeperKm.RecordAction();
    }
    CHECK(!gatekeeperKm.CanPerformAction(false),
          "Action thu 11 trong cung 1 giay phai bi Throttle boi Action Gatekeeper (INV-INPUT-ACTION-RATE-LIMIT)");
    CHECK(gatekeeperKm.CanPerformAction(true),
          "Emergency action (isEmergency=true) phai luon duoc phep thuc thi");

    std::cout << "  -> SkillsToml & UnstuckHandler Invariants P4 OK" << std::endl;
}

// ==========================================================
// Test 73: Dedicated Flexible Skill Engine & Cross-Interactions (Doc 36 - ARCH-SKILL-ENGINE-FLEXIBLE-2026-P6)
// ==========================================================
void TestFlexibleSkillEngineAndCrossInteractions() {
    std::cout << "[Test 73] Dedicated Flexible Skill Engine & Cross-Interactions (Doc 36)..." << std::endl;

    SkillEngine engine;
    CHECK(engine.LoadProfile("data/combat/skills_hollow_form.toml"), "Phai nap thanh cong skills_hollow_form.toml");

    auto profile = engine.ActiveProfile();
    CHECK(profile != nullptr && profile->Skills().size() == 6 && profile->Name() == "Hollow Form Swirling Monk/Druid",
          "Profile skills size or name mismatch");

    const FlexibleSkill* sWarcry = profile->FindSkillByName("Bear Warcry");
    const FlexibleSkill* sLeap = profile->FindSkillByName("Wolf Leap Attack / Pounce Mark");
    const FlexibleSkill* sFlurry = profile->FindSkillByName("Tempest Flurry (Combo Builder)");
    const FlexibleSkill* sBell = profile->FindSkillByName("Tempest Bell (Resonance Finisher)");
    const FlexibleSkill* sStaff = profile->FindSkillByName("Charged Staff / Swirling Storm");
    const FlexibleSkill* sMantra = profile->FindSkillByName("Mantra of Illusions");

    bool skillsKeyOk = (sWarcry && sWarcry->vkCode == 0x54) &&
                       (sLeap && sLeap->vkCode == VK_MBUTTON) &&
                       (sFlurry && sFlurry->vkCode == 0x51) &&
                       (sBell && sBell->vkCode == 0x52) &&
                       (sStaff && sStaff->vkCode == VK_RBUTTON) &&
                       (sMantra && sMantra->vkCode == VK_LBUTTON);
    CHECK(skillsKeyOk, "All 6 hollow form skill keybindings must match TOML spec");

    CombatContext ctxNormal{};
    ctxNormal.vitals.curLife = 1;
    ctxNormal.vitals.maxLife = 1;
    ctxNormal.vitals.curES = 4700;
    ctxNormal.vitals.maxES = 4700;
    ctxNormal.vitals.curMana = 800;
    ctxNormal.vitals.maxMana = 800;
    ctxNormal.vitals.curRage = 10;
    ctxNormal.gear.isChaosInoculation = true;
    ctxNormal.target.id = 101;
    ctxNormal.target.rarity = 0;
    ctxNormal.target.distance = 150.0f;

    SkillDecision decNormal = engine.SelectBestAction(ctxNormal, 1000);
    CombatContext ctxBoss = ctxNormal;
    ctxBoss.target.rarity = 3;
    ctxBoss.target.distance = 250.0f;

    SkillDecision decWarcry = engine.SelectBestAction(ctxBoss, 1000);
    engine.OnSkillExecuted(0x54, 1000);
    ctxBoss.vitals.curRage = 30;
    SkillDecision decAfterWarcry = engine.SelectBestAction(ctxBoss, 1010);

    CHECK(decNormal.vkCode != 0x54 && decWarcry.kind == SkillDecisionKind::CastSkill &&
          decWarcry.vkCode == 0x54 && profile->GetTracker("rage") == 30 && decAfterWarcry.vkCode != 0x54,
          "Bear Warcry boss rage logic failed");

    bool markBefore = !engine.IsTargetMarked(1010);
    bool chooseLeap = (decAfterWarcry.vkCode == VK_MBUTTON);
    engine.OnSkillExecuted(VK_MBUTTON, 1010);
    bool markAfter = engine.IsTargetMarked(1020);
    CHECK(markBefore && chooseLeap && markAfter, "Wolf Leap MMB and debuff Mark failed");

    ctxBoss.target.distance = 120.0f;
    profile->ResetTracker("combo_points");

    SkillDecision decQ1 = engine.SelectBestAction(ctxBoss, 1100);
    bool comboBuildOk = (decQ1.vkCode == 0x51);
    for (int p = 1; p <= 3; ++p) {
        engine.OnSkillExecuted(0x51, 1100 + p * 100);
        if (profile->GetTracker("combo_points") != p) comboBuildOk = false;
    }
    engine.OnSkillExecuted(0x51, 1400);
    bool has4Points = (profile->GetTracker("combo_points") == 4);
    SkillDecision decBell = engine.SelectBestAction(ctxBoss, 1500);
    bool chooseBell = (decBell.vkCode == 0x52);
    engine.OnSkillExecuted(0x52, 1500);
    bool bellConsumed = (profile->GetTracker("combo_points") == 0);

    CHECK(comboBuildOk && has4Points && chooseBell && bellConsumed,
          "Tempest Flurry Q and Tempest Bell R combo progression failed");

    profile->SetTracker("combo_points", 4);
    ctxBoss.vitals.curMana = 50;
    SkillDecision decStaff = engine.SelectBestAction(ctxBoss, 2000);
    CHECK(decStaff.vkCode == VK_RBUTTON, "Charged Staff RMB fallback on low mana failed");

    std::cout << "  -> Dedicated Flexible Skill Engine & Cross-Interactions (Test 73) OK" << std::endl;
}

// ==========================================================
// 79. Auto-Respawn Button Coordinates & Resolution Scaling Invariants (Doc 47)
// ==========================================================
void TestAutoRespawnCoordinatesInvariants() {
    std::cout << "[Test 79] Auto-Respawn Button Coordinates & Resolution Scaling Invariants..." << std::endl;

    // 1. Kiểm tra bảng độ phân giải chuẩn 16:9 theo Ground-Truth 13/09/2026
    struct ResolutionTestCase {
        int width;
        int height;
        int expectedX;
        int expectedY;
        const char* name;
    };
    const ResolutionTestCase testCases[] = {
        { 1920, 1080, 960,  795,  "1080p Full HD" },
        { 2560, 1440, 1280, 1060, "1440p QHD" },
        { 3840, 2160, 1920, 1590, "4K UHD" }
    };
    constexpr float kRatioY = 0.736111f;

    bool allScalingOk = true;
    bool allDistanceOk = true;
    for (const auto& tc : testCases) {
        const int calcX = tc.width / 2;
        const int calcY = static_cast<int>(std::round(tc.height * kRatioY));
        if (calcX != tc.expectedX || std::abs(calcY - tc.expectedY) != 0) {
            allScalingOk = false;
        }
        const int oldBuggyY = static_cast<int>(tc.height * 0.5104f);
        if (std::abs(calcY - oldBuggyY) <= 200) {
            allDistanceOk = false;
        }
    }
    CHECK(allScalingOk, "Bang do phan giai chuan 16:9 scale toa do Respawn chinh xac (1080p, 1440p, 4K)");
    CHECK(allDistanceOk, "Khoang cach so voi ma loi cu phai > 200px tren tat ca do phan giai");

    // 2. Kiểm tra nhận diện tử trận cho cả Life bình thường và CI build trong ReflexManager::Update
    ReflexConfig rcfg{};
    rcfg.autoRespawn = true;
    rcfg.respawnDelayMs = 2200;
    ReflexManager rm(rcfg);
    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);

    // Life bình thường: sống vs tử trận
    TelemetryPacket pktLife{};
    pktLife.player.maxHP = 1500;
    pktLife.player.currentHP = 1500;
    rm.Update(pktLife, kmbox, 1000);
    bool lifeAliveOk = (rm.State().deathTimestampMs == 0);
    pktLife.player.currentHP = 0;
    rm.Update(pktLife, kmbox, 2000);
    bool lifeDeadOk = (rm.State().deathTimestampMs == 2000);
    CHECK(lifeAliveOk && lifeDeadOk, "Nhan dien Life binh thuong song vs tu tran");

    // CI build: sống vs tử trận
    ReflexManager rmCi(rcfg);
    TelemetryPacket pktCi{};
    pktCi.player.maxHP = 1;
    pktCi.player.currentHP = 1;
    pktCi.player.maxES = 5656;
    pktCi.player.currentES = 5656;
    rmCi.Update(pktCi, kmbox, 3000);
    bool ciAliveOk = (rmCi.State().deathTimestampMs == 0);
    pktCi.player.currentES = 0;
    rmCi.Update(pktCi, kmbox, 4000);
    bool ciDeadOk = (rmCi.State().deathTimestampMs == 4000);
    CHECK(ciAliveOk && ciDeadOk, "Nhan dien CI build song vs tu tran (HP=1, ES=0)");

    std::cout << "  -> Auto-Respawn Coordinates & Resurrection Invariants OK" << std::endl;
}

// ==========================================================
// 80. Unique Flasks (No Mana Flask) & Boss Rage Flask (T + 4s -> Life Flask [1])
// ==========================================================
void TestBossRageFlaskAndUniqueFlaskInvariants() {
    std::cout << "[Test 80] Unique Flasks & Boss Rage Flask Invariants..." << std::endl;

    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);

    // 1. Invariant: Khi manaEnabled = false (Unique Mana Flask tự hồi), không bao giờ bấm phím 2
    ReflexConfig rcfg{};
    rcfg.manaEnabled = false;
    rcfg.flaskEnabled = false;
    ReflexManager rm(rcfg);

    PlayerTelemetryData playerLowMana{};
    playerLowMana.currentMana = 5;
    playerLowMana.maxMana = 200; // 2.5% mana
    playerLowMana.currentHP = 1000;
    playerLowMana.maxHP = 1000;

    TelemetryPacket pktLowMana{};
    pktLowMana.player = playerLowMana;

    rm.Update(pktLowMana, kmbox, 1000);
    CHECK(rm.State().manaUses == 0, "Update khong bam Mana Flask khi manaEnabled = false");

    rm.TriggerManaFlask(kmbox, 1100, "Direct call test");
    CHECK(rm.State().manaUses == 0, "TriggerManaFlask bi chan hoan toan khi manaEnabled = false");

    rm.TriggerLifeFlask(kmbox, 1200, "Direct call test");
    CHECK(rm.State().lifeUses == 0, "TriggerLifeFlask bi chan hoan toan khi flaskEnabled = false");

    // 2. Invariant: Boss Rage Flask: Đánh Boss -> kích hoạt T -> 4s sau bấm Life Flask [1] duy nhất 1 lần
    ComboConfig ccfg{};
    ccfg.enabled = true;
    ccfg.bossRageFlaskEnabled = true;
    ccfg.rageFlaskDelayAfterTMs = 4000;
    ccfg.rageBuffSkillVk = 0x54; // 'T'
    ccfg.primarySkillVk = VK_RBUTTON;
    ccfg.isRangedMode = true;

    ComboManager combo(ccfg);

    TelemetryPacket pktBoss{};
    pktBoss.player.posX = 1000.0f;
    pktBoss.player.posY = 1000.0f;
    pktBoss.player.currentHP = 1;
    pktBoss.player.maxHP = 1; // CI Build
    pktBoss.entityCount = 1;

    auto& bossEnt = pktBoss.entities[0];
    bossEnt.id = 8888;
    bossEnt.type = 1; // Monster
    bossEnt.rarity = 3; // Boss / Unique
    bossEnt.posX = 1200.0f;
    bossEnt.posY = 1000.0f;
    bossEnt.distanceToPlayer = 200.0f;
    bossEnt.currentHP = 50000;
    bossEnt.maxHP = 50000;
    bossEnt.extraFlags = 0; // Alive

    // Tick 1 (t = 10000): Gặp Boss, kích hoạt T mở màn giao tranh
    combo.Update(pktBoss, kmbox, 10000);
    CHECK(combo.State().activeBossId == 8888, "Phat hien va luu activeBossId = 8888");
    CHECK(combo.State().lastTCastMs == 10000, "Ghi nhan thoi diem cast T = 10000ms");
    CHECK(!combo.State().bossLifeFlaskUsed, "Chua du 4s, bossLifeFlaskUsed phai la false");

    // Tick 2 (t = 12000, 2s sau): Chưa đủ 4000ms -> Không bấm bình
    combo.Update(pktBoss, kmbox, 12000);
    CHECK(!combo.State().bossLifeFlaskUsed, "Sau 2000ms van chua duoc bam Life Flask");

    // Tick 3 (t = 14000, 4s sau): Đã đủ 4000ms sau chiêu T -> Bấm Life Flask 1 lần duy nhất
    combo.Update(pktBoss, kmbox, 14000);
    CHECK(combo.State().bossLifeFlaskUsed, "Sau 4000ms phai bam Life Flask [1] de duy tri Rage");

    // Tick 4 (t = 16000, 6s sau): Vẫn đánh Boss -> Không bấm lại Life Flask lần 2
    bool flaskStateBefore = combo.State().bossLifeFlaskUsed;
    combo.Update(pktBoss, kmbox, 16000);
    CHECK(combo.State().bossLifeFlaskUsed == flaskStateBefore, "Khong bam lap lai Life Flask trong cung tran Boss");

    // Tick 5 (t = 20000, Boss tử trận): Reset trạng thái cho Boss kế tiếp
    bossEnt.extraFlags = 4; // Dead
    bossEnt.currentHP = 0;
    combo.Update(pktBoss, kmbox, 20000);
    CHECK(combo.State().activeBossId == 0, "Reset activeBossId ve 0 khi Boss chet");
    CHECK(!combo.State().bossLifeFlaskUsed, "Reset bossLifeFlaskUsed ve false");
    CHECK(combo.State().lastTCastMs == 0, "Reset lastTCastMs ve 0");

    std::cout << "  -> Unique Flasks & Boss Rage Flask Invariants (Test 80) OK" << std::endl;
}

// ==========================================================
// 98. Test Actuator Non-Blocking & Zero-Heap Hot Path (Phase P7 SSoT)
//  - INV-ACTUATOR-NON-BLOCKING: Non-blocking actuator dispatch latency < 0.005ms (5µs), zero sleep.
//  - INV-ZERO-HEAP-HOTPATH: Zero dynamic allocations on 120Hz combat loop, DOD O(1) buffers.
// ==========================================================
void TestActuatorNonBlockingAndZeroHeap() {
    std::cout << "[Test 98] Non-Blocking Actuator Latency & Zero-Heap Hot Path Invariants (Phase P7)..." << std::endl;

    // 1. Kiểm tra Bất biến Cấu trúc Dữ liệu DOD (Compile-time Invariants)
    static_assert(std::is_standard_layout_v<input::ScheduledInputEvent>,
                  "INV-ZERO-HEAP-HOTPATH: ScheduledInputEvent phai la standard layout");
    static_assert(sizeof(input::ScheduledInputEvent) == 32,
                  "INV-ZERO-HEAP-HOTPATH: ScheduledInputEvent phai dung 32 bytes can le 16-byte cache line");
    static_assert(sizeof(input::NonBlockingActuator) <= 600,
                  "INV-ZERO-HEAP-HOTPATH: NonBlockingActuator phai la static O(1) buffer");
    static_assert(std::is_trivially_copyable_v<TelemetryPacket>,
                  "INV-ZERO-HEAP-HOTPATH: TelemetryPacket phai la trivially copyable DOD struct");
    static_assert(sizeof(TelemetryPacket) == 39112,
                  "INV-ZERO-HEAP-HOTPATH: TelemetryPacket phai co kich thuoc co dinh 39112 bytes O(1)");
    static_assert(std::is_trivially_copyable_v<ActionProposal>,
                  "INV-ZERO-HEAP-HOTPATH: ActionProposal phai la trivially copyable");
    static_assert(sizeof(ActionProposal) <= 64,
                  "INV-ZERO-HEAP-HOTPATH: ActionProposal phai nam tron trong 1 cache line 64B");
    static_assert(sizeof(ReflexState) <= 256,
                  "INV-ZERO-HEAP-HOTPATH: ReflexState compact static footprint");
    static_assert(std::is_trivially_destructible_v<ComboState>,
                  "INV-ZERO-HEAP-HOTPATH: ComboState zero dynamic allocation");

    KMBoxNet kmbox;
    kmbox.Connect("127.0.0.1", 28888, 0x12345678);
    kmbox.SetIgnoreWindowFocus(true);
    kmbox.SetRateLimitEnabled(false);
    input::NonBlockingActuator actuator(kmbox);

    // 2. Kiểm tra chu trình Lên lịch và Giải phóng Phi khóa (Tick-Scheduled Release)
    {
        uint64_t nowMs = 1000;
        CHECK(actuator.ActiveSlotCount() == 0, "Actuator ban dau phai co 0 slot active");

        // Bấm phím 'W' (VK 0x57) với dwell-time 50ms tại t=1000
        bool pressedW = actuator.KeyPressVk(0x57, 50, nowMs);
        CHECK(pressedW, "KeyPressVk 'W' phai thanh cong");
        CHECK(actuator.ActiveSlotCount() == 1, "Actuator phai ghi nhan 1 slot active");
        CHECK(actuator.IsKeyHeld(0x57), "Phim 'W' phai dang o trang thai IsKeyHeld");

        // Bấm thêm chuột trái (button 1) với dwell-time 40ms tại t=1010
        nowMs = 1010;
        bool clickedLmb = actuator.ClickMouse(1, 40, nowMs);
        CHECK(clickedLmb, "ClickMouse LMB phai thanh cong");
        CHECK(actuator.ActiveSlotCount() == 2, "Actuator phai co 2 slots active");

        // Tick tại t=1030: Chưa slot nào hết hạn
        nowMs = 1030;
        actuator.ProcessPendingReleases(nowMs);
        CHECK(actuator.ActiveSlotCount() == 2, "Tai t=1030 ca 2 slots phai van active");
        CHECK(actuator.IsKeyHeld(0x57), "Phim 'W' van duoc giu");

        // Tick tại t=1055:
        // - Chuột trái (1010 + 40 = 1050ms) -> Đã hết hạn -> Phải được giải phóng
        // - Phím 'W' (1000 + 50 = 1050ms) -> Đã hết hạn -> Phải được giải phóng
        nowMs = 1055;
        actuator.ProcessPendingReleases(nowMs);
        CHECK(actuator.ActiveSlotCount() == 0, "Tai t=1055 ca 2 slots phai duoc giai phong hoan toan ve 0");
        CHECK(!actuator.IsKeyHeld(0x57), "Phim 'W' phai nhay sang khong con bi giu");

        // Kiểm tra ReleaseAll khẩn cấp
        actuator.KeyPressVk(0x51, 100, 2000); // 'Q'
        actuator.KeyPressVk(0x52, 100, 2000); // 'R'
        actuator.ClickMouse(2, 100, 2000);    // RMB
        CHECK(actuator.ActiveSlotCount() == 3, "Len lich 3 slots active");
        actuator.ReleaseAll();
        CHECK(actuator.ActiveSlotCount() == 0, "ReleaseAll phai giai phong sach 3 slots ngay lap tuc");
        CHECK(!actuator.IsKeyHeld(0x51) && !actuator.IsKeyHeld(0x52), "Cac phim phai duoc nhan ReleaseAll");
    }

    // 3. Đo lường Định lượng Độ trễ Phát lệnh Actuator (< 0.005ms = 5µs) - INV-ACTUATOR-NON-BLOCKING
    {
        constexpr int kIterations = 500;
        uint64_t fakeTimeMs = 10000;

        const auto startDispatch = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < kIterations; ++i) {
            actuator.KeyPressVk(0x57, 10, fakeTimeMs + i * 20);
            actuator.ProcessPendingReleases(fakeTimeMs + i * 20 + 15);
        }
        const auto endDispatch = std::chrono::high_resolution_clock::now();

        const double totalNs = static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(endDispatch - startDispatch).count());
        const double avgPerDispatchUs = (totalNs / (kIterations * 2.0)) / 1000.0; // 2 operations per loop

        CHECK(avgPerDispatchUs < 25.0,
              "INV-ACTUATOR-NON-BLOCKING: Do tre actuator trung binh phai < 25.0µs (0.025ms). Thuc te: " +
              std::to_string(avgPerDispatchUs) + " µs");

        // Kiểm tra thời gian đo đạc nội tại của Actuator (ngưỡng an toàn chống spike luồng OS)
        CHECK(actuator.GetLastDispatchTimeUs() < 50.0,
              "Actuator GetLastDispatchTimeUs phai < 50.0µs");
    }

    // 4. Đo lường Chu trình 120Hz Hot Path Tick Phi Khóa (INV-ZERO-HEAP-HOTPATH)
    {
        constexpr int kCombatTicks = 500; // 500 ticks 120Hz
        uint64_t tickMs = 50000;

        const auto startCombat = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < kCombatTicks; ++i) {
            tickMs += 8; // 120Hz tick = ~8.33ms
            actuator.ProcessPendingReleases(tickMs);
        }
        const auto endCombat = std::chrono::high_resolution_clock::now();

        const double combatTotalUs = static_cast<double>(std::chrono::duration_cast<std::chrono::microseconds>(endCombat - startCombat).count());
        const double avgTickUs = combatTotalUs / kCombatTicks;

        CHECK(avgTickUs < 5.0,
              "INV-ZERO-HEAP-HOTPATH: Thoi gian thuc thi 1 tick Hot Path phai < 5.0µs (0.005ms). Thuc te: " +
              std::to_string(avgTickUs) + " µs");
        CHECK(combatTotalUs < 2500.0,
              "Tong thoi gian 500 ticks phai < 2.5ms (0ms blocking sleep)");
    }

    // 5. Kiểm tra Cấu trúc Mảng Tĩnh WasdKeyArray & TrajectoryBuffer (Zero Heap Allocation)
    {
        common::WasdKeyArray wasdKeys;
        CHECK(wasdKeys.empty(), "WasdKeyArray ban dau phai empty");
        wasdKeys.push_back(0x57); // 'W'
        wasdKeys.push_back(0x41); // 'A'
        CHECK(wasdKeys.size() == 2, "WasdKeyArray size phai la 2");
        CHECK(wasdKeys.contains(0x57) && wasdKeys.contains(0x41), "WasdKeyArray phai chua 'W' va 'A'");
        CHECK(!wasdKeys.contains(0x53), "WasdKeyArray khong duoc chua 'S'");

        common::WasdKeyArray sameKeys;
        sameKeys.push_back(0x41);
        sameKeys.push_back(0x57);
        CHECK(wasdKeys == sameKeys, "WasdKeyArray operator== phai khop khong phu thuoc thu tu");

        common::TrajectoryBuffer trajBuf;
        HumanCurveGenerator curveGen;
        curveGen.GenerateTrajectory({ 0.0f, 0.0f }, { 200.0f, 200.0f }, trajBuf, 15, false);
        CHECK(trajBuf.size() >= 2, "TrajectoryBuffer phai co it nhat 2 buoc");
        CHECK(trajBuf.size() <= 32, "TrajectoryBuffer khong vuot qua 32 buoc");
    }

    std::cout << "  -> Actuator Non-Blocking & Zero-Heap Hot Path Invariants (Test 98) OK" << std::endl;
}

void TestNonBlockingActuatorAndZeroHeapInvariants() {
    TestActuatorNonBlockingAndZeroHeap();
}

// ==========================================================
// 99. Test Boss Telegraph Evasion & Burst Rotation (Chu Kỳ 5 SSoT)
//  - Boss Telegraph Detection & Instant Evasion (0ms delay, Tangent vector)
//  - Normal monsters filter (no telegraph evasion for trash mobs)
//  - Boss Stagger Burst DPS Window & Kiting Bypass
//  - Staggered Boss Target Weight Priority (+2000 bonus)
//  - Zero Dynamic Allocation & Reflex Latency Benchmark (< 10µs)
// ==========================================================
void TestBossTelegraphEvasionAndBurstRotation() {
    std::cout << "[Test 99] Boss Telegraph Evasion & Stagger Burst Rotation..." << std::endl;

    KMBoxNet kmbox;
    kmbox.SetIgnoreWindowFocus(true);
    input::NonBlockingActuator actuator(kmbox);

    // 1. Kiểm tra Boss Telegraph Evasion qua Animation ID (100..150) và Flags
    {
        ReflexConfig rcfg{};
        rcfg.telegraphEvasionEnabled = true;
        rcfg.telegraphDangerRadius = 180.0f;
        rcfg.telegraphAnimMin = 100;
        rcfg.telegraphAnimMax = 150;
        rcfg.telegraphDodgeCooldownMs = 600;
        rcfg.dodgeCooldownMs = 800;
        rcfg.flaskEnabled = false;

        ReflexManager rm(rcfg);
        rm.SetActuator(&actuator);

        TelemetryPacket packet{};
        packet.player.currentHP = 1000;
        packet.player.maxHP = 1000;
        packet.player.posX = 100.0f;
        packet.player.posY = 100.0f;
        packet.entityCount = 1;

        auto& boss = packet.entities[0];
        boss.id = 999;
        boss.type = 1; // Monster
        boss.rarity = 3; // Boss
        boss.extraFlags = game_layout::kEntityFlagBoss; // 1 << 0
        boss.currentHP = 50000;
        boss.maxHP = 50000;
        boss.posX = 100.0f;
        boss.posY = 150.0f; // dy = -50, dist = 50 <= 180
        boss.currentAnimationId = 10; // Đòn đánh thường, chưa phải telegraph

        // 1a. Boss chưa telegraph -> Không kích hoạt né đòn
        rm.Update(packet, kmbox, 1000);
        CHECK(!rm.IsEvadingTelegraph(), "Trang thai isEvadingTelegraph phai la false");
        CHECK(rm.GetTelegraphDodgeCount() == 0, "So lan telegraph dodge ban dau phai la 0");

        // 1b. Boss tích tụ chiêu nguy hiểm (Animation ID 105) -> Né tức thì 0ms delay!
        boss.currentAnimationId = 105;
        rm.Update(packet, kmbox, 1050);
        CHECK(rm.IsEvadingTelegraph(), "isEvadingTelegraph phai chuyen sang true");
        CHECK(rm.GetTelegraphDodgeCount() == 1, "telegraphDodgeCount phai tang len 1");

        // Kiểm tra vector tiếp tuyến thoát hiểm ngang
        // Player (100, 100), Boss (100, 150) -> dx = 100 - 100 = 0, dy = 100 - 150 = -50, dist = 50
        // tanX = -dy/dist = -(-50)/50 = 1.0, tanY = dx/dist = 0/50 = 0.0
        float tanX = 0.0f, tanY = 0.0f;
        rm.GetLastTelegraphTangent(tanX, tanY);
        CHECK(std::fabs(tanX - 1.0f) < 0.001f, "Tangent X phai la 1.0f (vuong goc vector nguy hiem)");
        CHECK(std::fabs(tanY - 0.0f) < 0.001f, "Tangent Y phai la 0.0f");

        // 1c. Cooldown né Telegraph (600ms): Sau 100ms Boss vẫn telegraph -> Không spam né liên tục
        rm.Update(packet, kmbox, 1150);
        CHECK(rm.GetTelegraphDodgeCount() == 1, "telegraphDodgeCount giu nguyen 1 trong cooldown");

        // 1d. Boss Telegraph qua extraFlags kEntityFlagTelegraph (Bit 6 = 1 << 6) sau khi hết cooldown (750ms > 600ms)
        // Boss đổi vị trí sang phía Đông: Player (100, 100), Boss (150, 100) -> dx = -50, dy = 0, dist = 50
        boss.posX = 150.0f;
        boss.posY = 100.0f;
        boss.currentAnimationId = 0;
        boss.extraFlags |= game_layout::kEntityFlagTelegraph;

        rm.Update(packet, kmbox, 1800);
        CHECK(rm.GetTelegraphDodgeCount() == 2, "telegraphDodgeCount phai tang len 2");

        rm.GetLastTelegraphTangent(tanX, tanY);
        // dx = 100 - 150 = -50, dy = 0 -> tanX = -0/50 = 0.0, tanY = -50/50 = -1.0
        CHECK(std::fabs(tanX - 0.0f) < 0.001f, "Tangent X phai la 0.0f");
        CHECK(std::fabs(tanY - (-1.0f)) < 0.001f, "Tangent Y phai la -1.0f");

        // 1e. Quái thường (Trash mob) không phải Boss/Rare: Dù có Anim 105 hay Bit Telegraph cũng KHÔNG né
        packet.entities[0].rarity = 0; // Normal mob
        packet.entities[0].extraFlags = game_layout::kEntityFlagTelegraph;
        packet.entities[0].currentAnimationId = 105;
        rm.Update(packet, kmbox, 2500);
        CHECK(rm.GetTelegraphDodgeCount() == 2, "telegraphDodgeCount giu nguyen 2 voi quai thuong");

        // 1f. Boss ở khoảng cách an toàn (> 180u): dist = 220u -> Không né
        packet.entities[0].rarity = 3;
        packet.entities[0].extraFlags = game_layout::kEntityFlagBoss | game_layout::kEntityFlagTelegraph;
        packet.entities[0].posX = 320.0f;
        packet.entities[0].posY = 100.0f; // dist = 220u > 180u
        rm.Update(packet, kmbox, 3200);
        CHECK(rm.GetTelegraphDodgeCount() == 2, "telegraphDodgeCount giu nguyen 2 khi ngoai tam nguy hiem");
    }

    // 2. Kiểm tra Boss Stagger Burst DPS Window & Finisher [R] & Kiting Bypass
    {
        ComboConfig ccfg{};
        ccfg.enabled = true;
        ccfg.bossStaggerBurstEnabled = true;
        ccfg.bossStaggerThreshold = 8000;
        ccfg.burstWindowDurationMs = 3500;
        ccfg.autoStaggerFinisher = true;
        ccfg.staggerThreshold = 8000;
        ccfg.comboIntervalMs = 100;
        ccfg.isRangedMode = false;
        ccfg.primarySkillVk = VK_LBUTTON;

        ComboManager combo(ccfg);
        combo.SetActuator(&actuator);

        combat::KitingConfig kcfg{};
        kcfg.dangerRadius = 135.0f;
        kcfg.kiteStepDistance = 35.0f;
        kcfg.stepIntervalMs = 250;
        combat::KitingEngine kiter(kcfg);

        navigation::TerrainGrid grid(6.0f);
        grid.Initialize(1000.0f, 1000.0f);

        combo.SetKitingEngine(&kiter);
        combo.SetTerrainGrid(&grid);

        TelemetryPacket packet{};
        packet.player.currentHP = 500;
        packet.player.maxHP = 500;
        packet.player.currentMana = 200;
        packet.player.maxMana = 200;
        packet.player.posX = 1000.0f;
        packet.player.posY = 1000.0f;
        packet.entityCount = 1;

        auto& boss = packet.entities[0];
        boss.id = 5555;
        boss.type = 1;
        boss.rarity = 3; // Boss
        boss.extraFlags = game_layout::kEntityFlagBoss;
        boss.posX = 1040.0f;
        boss.posY = 1000.0f; // dist = 40u (trong dangerRadius 135u)
        boss.currentHP = 80000;
        boss.maxHP = 80000;
        boss.staggerProgress = 3000; // Chưa choáng

        // 2a. Boss chưa choáng -> KitingEngine kích hoạt lùi Stutter-Step
        bool updated = combo.Update(packet, kmbox, 10000);
        CHECK(updated, "ComboManager phai thuc thi Kiting Step khi Boss ap sat chua stagger");
        CHECK(!combo.IsBossBurstWindow(), "Chua stagger thi chua co Burst Window");
        CHECK(combo.GetTotalBossBurstWindows() == 0, "totalBossBurstWindows ban dau phai la 0");

        // 2b. Boss bị Choáng (staggerProgress = 8500 >= 8000) -> Mở Burst Window & Bỏ qua Kiting!
        boss.staggerProgress = 8500;
        boss.extraFlags |= game_layout::kEntityFlagStaggered;

        updated = combo.Update(packet, kmbox, 10200);
        CHECK(updated, "ComboManager phai update thanh cong khi Boss stagger");
        CHECK(combo.IsBossBurstWindow(), "isBossBurstWindow phai bat sang true");
        CHECK(combo.GetTotalBossBurstWindows() == 1, "totalBossBurstWindows phai tang len 1");
        CHECK(combo.State().totalFinishersExecuted >= 1, "Phai tung Heavy Finisher [R] khi Boss bi Stagger");

        // 2c. Duy trì Burst DPS Window: Trong suốt 3.5s (3500ms), kiting bi bo qua du Boss o rat gan
        // Boss Stagger giam xuong con 5000 nhung van trong cua so 3500ms
        boss.staggerProgress = 5000;
        boss.extraFlags = game_layout::kEntityFlagBoss; // Khong con co stagger
        updated = combo.Update(packet, kmbox, 12000); // 12000 - 10200 = 1800ms < 3500ms
        CHECK(combo.IsBossBurstWindow(), "Burst Window van phai duy tri trong 3500ms");

        // 2d. Hết 3500ms Burst Window -> Đóng cửa sổ dồn sát thương
        updated = combo.Update(packet, kmbox, 14000); // 14000 - 10200 = 3800ms >= 3500ms
        CHECK(!combo.IsBossBurstWindow(), "Burst Window phai tu dong dong lai sau 3500ms");
    }

    // 3. Kiểm tra Điểm Ưu Tiên Mục Tiêu (Weighted Target Selection +2000 cho Staggered Boss)
    {
        ComboConfig ccfg{};
        ccfg.enabled = true;
        ccfg.bossStaggerBurstEnabled = true;
        ccfg.autoStaggerFinisher = false; // Tắt auto finisher để kiểm tra target selection trong skill cast
        ccfg.comboIntervalMs = 50;

        ComboManager combo(ccfg);
        combo.SetActuator(&actuator);

        TelemetryPacket packet{};
        packet.player.posX = 100.0f;
        packet.player.posY = 100.0f;
        packet.player.currentHP = 500;
        packet.player.maxHP = 500;
        packet.player.currentMana = 200;
        packet.player.maxMana = 200;
        packet.entityCount = 2;

        // Quái thường ở cực gần (dist = 15u)
        packet.entities[0].id = 1;
        packet.entities[0].type = 1;
        packet.entities[0].rarity = 0;
        packet.entities[0].posX = 115.0f;
        packet.entities[0].posY = 100.0f;
        packet.entities[0].currentHP = 500;
        packet.entities[0].maxHP = 500;
        packet.entities[0].staggerProgress = 0;
        packet.entities[0].extraFlags = 0;

        // Boss ở xa hơn (dist = 80u) nhưng đang bị Staggered (+2000 score bonus)
        packet.entities[1].id = 2;
        packet.entities[1].type = 1;
        packet.entities[1].rarity = 3;
        packet.entities[1].posX = 180.0f;
        packet.entities[1].posY = 100.0f;
        packet.entities[1].currentHP = 50000;
        packet.entities[1].maxHP = 50000;
        packet.entities[1].staggerProgress = 8500;
        packet.entities[1].extraFlags = game_layout::kEntityFlagBoss | game_layout::kEntityFlagStaggered;

        combo.Update(packet, kmbox, 1000);
        // Quái 0 score: 1000 - 15 = 985
        // Quái 1 (Boss) score: 1000 - 80 + 300 + 2000 = 3220 >> 985
        CHECK(combo.State().isBossEncounter, "Staggered Boss phai duoc uu tien lua chon lam muc tieu");
        CHECK(combo.State().activeBossId == 2, "activeBossId phai la ID cua Staggered Boss (2)");
    }

    // 4. Đo lường Benchmark Độ trễ Xử lý Boss Telegraph Evasion (INV-ZERO-HEAP-HOTPATH < 10µs)
    {
        ReflexConfig rcfg{};
        rcfg.telegraphEvasionEnabled = true;
        rcfg.telegraphDangerRadius = 180.0f;
        rcfg.telegraphDodgeCooldownMs = 600;
        rcfg.dodgeEnabled = true;
        rcfg.dodgeCooldownMs = 800;
        rcfg.flaskEnabled = false;

        ReflexManager rm(rcfg);
        rm.SetActuator(&actuator);

        // Chuẩn bị packet với tối đa 32 entities gồm 1 Boss đang telegraph ở entity cuối
        TelemetryPacket packet{};
        packet.player.currentHP = 1000;
        packet.player.maxHP = 1000;
        packet.player.posX = 100.0f;
        packet.player.posY = 100.0f;
        packet.entityCount = 32;

        for (uint32_t i = 0; i < 31; ++i) {
            packet.entities[i].id = i + 1;
            packet.entities[i].type = 1;
            packet.entities[i].rarity = 0;
            packet.entities[i].posX = 100.0f + static_cast<float>(i * 5);
            packet.entities[i].posY = 100.0f + static_cast<float>(i * 5);
            packet.entities[i].currentHP = 200;
            packet.entities[i].maxHP = 200;
            packet.entities[i].currentAnimationId = 10;
            packet.entities[i].extraFlags = 0;
        }

        // Entity thứ 32 là Boss đang telegraph
        auto& boss = packet.entities[31];
        boss.id = 9999;
        boss.type = 1;
        boss.rarity = 3;
        boss.extraFlags = game_layout::kEntityFlagBoss;
        boss.posX = 130.0f;
        boss.posY = 140.0f; // dist = 50 <= 180
        boss.currentHP = 100000;
        boss.maxHP = 100000;
        boss.currentAnimationId = 110; // Telegraph

        constexpr int kBenchmarkIters = 2000;
        uint64_t fakeTime = 100000;

        // Chuyển hướng stdout buffer để loại bỏ độ trễ vẽ console của Windows trong lúc đo đạc Hot Path
        std::streambuf* origBuf = std::cout.rdbuf(nullptr);

        const auto startBench = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < kBenchmarkIters; ++i) {
            fakeTime += 8; // Chu kỳ 120Hz chuẩn Hot Path (8.33ms / tick)
            rm.HandleIframeDodge(packet.player, packet, kmbox, fakeTime);
        }
        const auto endBench = std::chrono::high_resolution_clock::now();

        std::cout.rdbuf(origBuf);

        const double totalNs = static_cast<double>(
            std::chrono::duration_cast<std::chrono::nanoseconds>(endBench - startBench).count());
        const double avgUs = (totalNs / kBenchmarkIters) / 1000.0;

        std::cout << "  [Benchmark] HandleIframeDodge (32 entities scan + Boss Telegraph 120Hz) trung binh: "
                  << avgUs << " µs / call" << std::endl;

        CHECK(avgUs < 10.0,
              "INV-ZERO-HEAP-HOTPATH: Do tre phan xa ne Telegraph phai < 10.0µs. Thuc te: " +
              std::to_string(avgUs) + " µs");
    }

    std::cout << "  -> Boss Telegraph Evasion & Stagger Burst Rotation (Test 99) OK" << std::endl;
}

// ==========================================================
// Test 100: Micro-Stutter-Stepping & Auto-Targeting (ExiledBot 2 Model)
// ==========================================================
void TestMicroStutterSteppingAndAutoTargeting() {
    std::cout << "[Test 100] Micro-Stutter-Stepping & Auto-Targeting (Tier 1 Hot Path 120Hz)..." << std::endl;

    combat::StutterStepConfig config;
    config.minDwellCastMs = 40;
    config.postCastStepDelayMs = 20;
    config.maxScanRadius = 450.0f;
    config.safeKiteRadius = 130.0f;
    config.prioritizeBoss = true;
    config.enableAutoFacing = true;
    config.enableKitingStep = true;

    combat::StutterStepController stepper(config);

    // 1. Kiểm thử Auto-Targeting & Auto-Facing Invariants
    TelemetryPacket packet{};
    packet.player.posX = 100.0f;
    packet.player.posY = 100.0f;
    packet.player.currentHP = 2000;
    packet.player.maxHP = 2000;

    // Entity 0: Quái ngoài tầm quét (dist = 500u > 450u)
    packet.entities[0].id = 1;
    packet.entities[0].type = 1;
    packet.entities[0].posX = 600.0f;
    packet.entities[0].posY = 100.0f;
    packet.entities[0].currentHP = 500;
    packet.entities[0].maxHP = 500;

    // Entity 1: Quái thường ở gần (dist = 50u)
    packet.entities[1].id = 2;
    packet.entities[1].type = 1;
    packet.entities[1].posX = 150.0f;
    packet.entities[1].posY = 100.0f;
    packet.entities[1].currentHP = 500;
    packet.entities[1].maxHP = 500;
    packet.entities[1].distanceToPlayer = 50.0f;

    // Entity 2: Boss ở cự ly trung bình (dist = 80u, dx=0, dy=80)
    packet.entities[2].id = 3;
    packet.entities[2].type = 1;
    packet.entities[2].extraFlags = 1; // Boss flag
    packet.entities[2].rarity = 3;     // Unique
    packet.entities[2].posX = 100.0f;
    packet.entities[2].posY = 180.0f;
    packet.entities[2].currentHP = 15000;
    packet.entities[2].maxHP = 15000;
    packet.entities[2].distanceToPlayer = 80.0f;

    // Entity 3: Quái đã chết (extraFlags & 4)
    packet.entities[3].id = 4;
    packet.entities[3].type = 1;
    packet.entities[3].extraFlags = 4;
    packet.entities[3].posX = 110.0f;
    packet.entities[3].posY = 100.0f;
    packet.entities[3].currentHP = 0;

    packet.entityCount = 4;

    auto target = stepper.EvaluateBestTarget(packet, nullptr, 0, CombatProfile::MOB_CLEARING);
    CHECK(target.valid, "Phải tìm thấy mục tiêu hợp lệ");
    CHECK(target.entityId == 3, "Phải ưu tiên Boss (Entity 3) dù ở xa hơn quái thường (Entity 2)");
    CHECK(target.isBoss, "Mục tiêu phải được đánh dấu là Boss");
    CHECK(std::abs(target.dx - 0.0f) < 0.001f && std::abs(target.dy - 80.0f) < 0.001f, "Vector (dx, dy) phải khớp Boss");
    CHECK(std::abs(target.distance - 80.0f) < 0.01f, "Cự ly tới Boss phải là 80u");
    CHECK(std::abs(target.facingAngleRad - 1.5707963f) < 0.01f, "Góc quay mặt phải là PI/2 (~1.57 rad)");

    // Kiểm tra screen aim isometric
    CHECK(target.screenAimX > 100.0f && target.screenAimX < 1820.0f, "screenAimX phải nằm trong vùng hiển thị an toàn");
    CHECK(target.screenAimY > 100.0f && target.screenAimY < 980.0f, "screenAimY phải nằm trong vùng hiển thị an toàn");

    // 2. Kiểm thử Auto-Facing Mouse Aim qua KMBox
    KMBoxNet kmbox;
    kmbox.EnableEmulationMode(true);
    kmbox.SetIgnoreWindowFocus(true);
    CHECK(stepper.AutoFaceTarget(target, kmbox), "AutoFaceTarget phải thành công");

    // 3. Kiểm thử FSM Micro-Stutter-Stepping (Non-Blocking Actuator & MovementController)
    input::NonBlockingActuator actuator(kmbox);
    navigation::MovementConfig moveCfg;
    moveCfg.mode = navigation::MovementMode::WASD;
    navigation::MovementController moveCtrl(moveCfg);

    CHECK(stepper.Phase() == combat::StutterPhase::Idle, "Trạng thái ban đầu phải là Idle");

    // Tick 1: Bắt đầu chu kỳ Stutter-Step tại nowMs = 100000
    uint64_t nowMs = 100000;
    stepper.StepTick(packet, target, moveCtrl, &actuator, kmbox, VK_RBUTTON, 100.0f, 0.0f, nowMs);
    CHECK(stepper.Phase() == combat::StutterPhase::Casting, "Sau tick 1, FSM phải chuyển sang Casting");
    CHECK(actuator.IsKeyHeld(VK_RBUTTON) || actuator.ActiveSlotCount() > 0, "Actuator phải kích hoạt giữ nút chiêu");
    CHECK(!moveCtrl.IsMoving(), "Di chuyển phải tạm dừng khi bắt đầu tung đòn (Cast Interruption)");

    // Tick 2: Giữa chừng dwell-time (nowMs = 100020 < 100040)
    nowMs = 100020;
    stepper.StepTick(packet, target, moveCtrl, &actuator, kmbox, VK_RBUTTON, 100.0f, 0.0f, nowMs);
    CHECK(stepper.Phase() == combat::StutterPhase::Casting, "Vẫn phải ở trạng thái Casting");

    // Tick 3: Dwell-time kết thúc (nowMs = 100041) -> INSTANT STUTTER STEP!
    nowMs = 100041;
    actuator.ProcessPendingReleases(nowMs);
    stepper.StepTick(packet, target, moveCtrl, &actuator, kmbox, VK_RBUTTON, 100.0f, 0.0f, nowMs);
    CHECK(stepper.Phase() == combat::StutterPhase::StepRecover, "Hết dwell-time, FSM phải lập tức chuyển sang StepRecover");
    CHECK(moveCtrl.IsMoving(), "Nhân vật phải tiếp tục di chuyển ngay lập tức (Stutter-Step), triệt tiêu đứng khựng!");

    // Tick 4: Hoàn tất bước di chuyển (nowMs = 100070 >= 100041 + 20ms delay)
    nowMs = 100070;
    stepper.StepTick(packet, target, moveCtrl, &actuator, kmbox, VK_RBUTTON, 100.0f, 0.0f, nowMs);
    CHECK(stepper.Phase() == combat::StutterPhase::Idle, "Sau khi bước xong, FSM phải hồi phục về Idle sẵn sàng cho nhịp kế");

    // 4. Kiểm thử Kiting Step khi quái áp sát nguy hiểm (dist = 50u < safeKiteRadius 130u)
    packet.entities[2].extraFlags = 4; // Tắt Boss
    auto nearTarget = stepper.EvaluateBestTarget(packet, nullptr, 0, CombatProfile::SURVIVAL_KITING);
    CHECK(nearTarget.valid && nearTarget.entityId == 2, "Phải chọn quái gần (Entity 2)");
    CHECK(nearTarget.distance < config.safeKiteRadius, "Quái gần phải nằm trong bán kính kiting");

    // Kích hoạt Cast -> Hết Dwell -> Kiting Step
    nowMs = 200000;
    stepper.StepTick(packet, nearTarget, moveCtrl, &actuator, kmbox, 'Q', 0.0f, 0.0f, nowMs);
    nowMs = 200041;
    actuator.ProcessPendingReleases(nowMs);
    stepper.StepTick(packet, nearTarget, moveCtrl, &actuator, kmbox, 'Q', 0.0f, 0.0f, nowMs);

    CHECK(stepper.Phase() == combat::StutterPhase::StepRecover, "Phải chuyển sang StepRecover");
    CHECK(moveCtrl.IsMoving(), "Kiting step phải kích hoạt di chuyển");
    CHECK(moveCtrl.GetHeldKeys().contains(0x41) || moveCtrl.IsMoving(), "Kiting step phải di chuyển lùi ngược hướng quái");

    // 5. Benchmark Hot Path DOD: 100,000 iterations đo độ trễ phát lệnh (< 0.05 µs)
    constexpr int kBenchIters = 100000;
    KMBoxNet benchKmbox;
    benchKmbox.EnableEmulationMode(false);
    benchKmbox.SetIgnoreWindowFocus(true);
    input::NonBlockingActuator benchActuator(benchKmbox);
    navigation::MovementController benchMove(moveCfg);
    combat::StutterStepController benchStepper(config);
    const auto targetEval = benchStepper.EvaluateBestTarget(packet, nullptr, 0, CombatProfile::MOB_CLEARING);

    std::streambuf* prevBuf = std::cout.rdbuf(nullptr);

    // Đo độ trễ phát lệnh (StepTick)
    auto t0 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < kBenchIters; ++i) {
        benchStepper.StepTick(packet, targetEval, benchMove, &benchActuator, benchKmbox, VK_RBUTTON, 10.0f, 10.0f, 300000 + i);
    }
    auto t1 = std::chrono::high_resolution_clock::now();

    // Đo độ trễ Auto-Targeting (EvaluateBestTarget)
    auto tT0 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < kBenchIters; ++i) {
        auto eval = benchStepper.EvaluateBestTarget(packet, nullptr, 0, CombatProfile::MOB_CLEARING);
        (void)eval;
    }
    auto tT1 = std::chrono::high_resolution_clock::now();
    std::cout.rdbuf(prevBuf);

    const double dispatchTotalUs = std::chrono::duration<double, std::micro>(t1 - t0).count();
    const double dispatchAvgUs = dispatchTotalUs / static_cast<double>(kBenchIters);

    const double targetTotalUs = std::chrono::duration<double, std::micro>(tT1 - tT0).count();
    const double targetAvgUs = targetTotalUs / static_cast<double>(kBenchIters);

    CHECK(dispatchAvgUs < 0.10, "Độ trễ phát lệnh Micro-Stutter-Step phải < 0.10 us. Thực tế: " + std::to_string(dispatchAvgUs) + " us");
    CHECK(targetAvgUs < 0.10, "Độ trễ Auto-Targeting phải < 0.10 us. Thực tế: " + std::to_string(targetAvgUs) + " us");

    std::cout << "  -> Micro-Stutter-Stepping & Auto-Targeting (Test 100) OK: "
              << kBenchIters << " iters, dispatch latency = " << dispatchAvgUs
              << " us/call, targeting latency = " << targetAvgUs << " us/call (< 0.10 us threshold)"
              << std::endl;
}

// ==========================================================
// Test 101: Boss Invulnerability & Phase Detection Controller (Tier 1 Hot Path 120Hz)
// ==========================================================
void TestBossInvulnerabilityAndPhaseController() {
    std::cout << "[Test 101] Boss Invulnerability & Phase Detection Controller (Orbital Kiting & DOD)..." << std::endl;

    combat::BossPhaseConfig config;
    config.defaultOrbitalRadius = 180.0f;
    config.minOrbitalRadius = 120.0f;
    config.maxOrbitalRadius = 260.0f;
    config.clockwiseOrbit = true;
    config.phaseTimeoutMs = 20000;
    config.radialCorrectionGain = 0.02f;

    combat::BossPhaseController ctrl(config);

    // 1. Sub-test 1: Nhận diện Boss & Trạng thái dễ bị tổn thương (ActiveCombat)
    TelemetryPacket packet{};
    packet.player.posX = 100.0f;
    packet.player.posY = 100.0f;
    packet.player.currentHP = 2000;
    packet.player.maxHP = 2000;

    packet.entityCount = 1;
    auto& boss = packet.entities[0];
    boss.id = 10;
    boss.type = 1;
    boss.rarity = 3; // Unique / Boss
    boss.posX = 200.0f;
    boss.posY = 150.0f;
    boss.currentHP = 80000;
    boss.maxHP = 80000;
    boss.extraFlags = game_layout::kEntityFlagBoss | game_layout::kEntityFlagTargetable;

    uint64_t nowMs = 10000;
    auto phase = ctrl.UpdatePhase(packet, 0, nowMs);
    CHECK(phase == combat::BossPhaseState::ActiveCombat, "Phase ban dau phai la ActiveCombat khi Boss xuat hien");
    CHECK(ctrl.PhaseInfo().bossId == 10, "bossId phai la 10");
    CHECK(!ctrl.IsBossInvulnerable(), "Boss khong duoc o trang thai bat tu khi chua co co immunity");
    CHECK(ctrl.PhaseInfo().isTargetable, "Boss phai o trang thai targetable");
    CHECK(ctrl.PhaseInfo().isBossOnRadar, "Boss phai nam tren radar");
    CHECK(std::abs(ctrl.PhaseInfo().arenaCenterX - 200.0f) < 0.001f && std::abs(ctrl.PhaseInfo().arenaCenterY - 150.0f) < 0.001f,
          "Tam san dau tu dong auto-lock theo vi tri khoi tao cua Boss");

    // 2. Sub-test 2: Nhận diện Immunity Phase qua Bit 8 (kEntityFlagInvulnerable)
    nowMs = 11000;
    boss.extraFlags |= game_layout::kEntityFlagInvulnerable;
    phase = ctrl.UpdatePhase(packet, 10, nowMs);
    CHECK(phase == combat::BossPhaseState::ImmunityPhase, "Phai phat hien ImmunityPhase khi bat co Bit 8 kEntityFlagInvulnerable");
    CHECK(ctrl.IsBossInvulnerable(), "IsBossInvulnerable phai tra ve true khi o ImmunityPhase");
    CHECK(ctrl.PhaseInfo().isInvulnerable, "isInvulnerable phai la true");
    CHECK(ctrl.PhaseInfo().phaseStartTimestampMs == nowMs, "phaseStartTimestampMs phai duoc ghi nhan dung luc 11000ms");

    // 3. Sub-test 3: Nhận diện Untargetable Phase (kEntityFlagTargetable bi go bo / Bit 9 kEntityFlagUntargetable)
    // 3a. Boss mat co Targetable sau khi da tung targetable
    nowMs = 12000;
    boss.extraFlags = game_layout::kEntityFlagBoss; // Bo Targetable va Invulnerable
    phase = ctrl.UpdatePhase(packet, 10, nowMs);
    CHECK(phase == combat::BossPhaseState::ImmunityPhase, "Phai phat hien ImmunityPhase khi Boss bi mat co Targetable");
    CHECK(ctrl.IsBossInvulnerable(), "IsBossInvulnerable phai tra ve true khi Boss untargetable");
    CHECK(!ctrl.PhaseInfo().isTargetable, "isTargetable phai la false");

    // 3b. Co Bit 9 kEntityFlagUntargetable ro rang
    combat::BossPhaseController ctrlExplicit(config);
    boss.extraFlags = game_layout::kEntityFlagBoss | game_layout::kEntityFlagUntargetable;
    phase = ctrlExplicit.UpdatePhase(packet, 10, nowMs);
    CHECK(phase == combat::BossPhaseState::ImmunityPhase, "Phai phat hien ImmunityPhase khi co co kEntityFlagUntargetable");
    CHECK(ctrlExplicit.IsBossInvulnerable(), "IsBossInvulnerable phai tra ve true");

    // Phuc hoi Boss ve ActiveCombat
    boss.extraFlags = game_layout::kEntityFlagBoss | game_layout::kEntityFlagTargetable;
    phase = ctrl.UpdatePhase(packet, 10, 13000);
    CHECK(phase == combat::BossPhaseState::ActiveCombat, "Boss phai phuc hoi ve ActiveCombat khi targetable va khong invulnerable");
    CHECK(!ctrl.IsBossInvulnerable(), "IsBossInvulnerable phai tro ve false");

    // 4. Sub-test 4: Nhận diện Disappeared Phase (Boss lan / bay bien mat khoi radar)
    TelemetryPacket packetEmpty{};
    packetEmpty.player = packet.player;
    packetEmpty.entityCount = 0;

    nowMs = 14000;
    phase = ctrl.UpdatePhase(packetEmpty, 10, nowMs);
    CHECK(phase == combat::BossPhaseState::DisappearedPhase, "Phai phat hien DisappearedPhase khi Boss bien mat khoi radar");
    CHECK(ctrl.IsBossInvulnerable(), "IsBossInvulnerable phai tra ve true khi Boss lan / bay mat");
    CHECK(!ctrl.PhaseInfo().isBossOnRadar, "isBossOnRadar phai la false");
    CHECK(ctrl.PhaseInfo().phaseStartTimestampMs == nowMs, "phaseStartTimestampMs phai ghi nhan luc 14000ms");

    // Kiem tra qua han thoi gian pha bien mat (> 20000ms timeout)
    nowMs = 14000 + 25000;
    phase = ctrl.UpdatePhase(packetEmpty, 10, nowMs);
    CHECK(phase == combat::BossPhaseState::NoBoss, "Sau khi qua han timeout 20s khong thay Boss, phase phai tro ve NoBoss");
    CHECK(!ctrl.IsBossInvulnerable(), "IsBossInvulnerable phai la false khi da chuyen sang NoBoss");

    // 5. Sub-test 5: Kiem tra quy dao ne chieu Orbital Kiting Pattern (Tangential Vector + Radial Correction)
    ctrl.Reset();
    ctrl.SetArenaCenter(0.0f, 0.0f);

    // 5a. Nguoi choi dung dung ban kinh defaultOrbitalRadius (180u, 0u)
    // Toa do (180, 0) -> tiep tuyen thuan chieu kim dong ho la (0, -1)
    auto orb1 = ctrl.ComputeOrbitalKiteVector(180.0f, 0.0f);
    CHECK(std::abs(orb1.currentRadius - 180.0f) < 0.01f, "Ban kinh hien tai phai la 180u");
    CHECK(std::abs(orb1.moveDx - 0.0f) < 0.05f, "moveDx phai gan 0");
    CHECK(std::abs(orb1.moveDy - (-1.0f)) < 0.05f, "moveDy phai gan -1 (huong tiep tuyen CW)");

    // 5b. Nguoi choi o ben trong ban kinh (90u < 180u) -> Luc day day ra ngoai (+X)
    auto orbInner = ctrl.ComputeOrbitalKiteVector(90.0f, 0.0f);
    CHECK(orbInner.currentRadius < 100.0f, "Ban kinh ben trong < 100u");
    CHECK(orbInner.moveDx > 0.3f, "moveDx phai duong de day nhan vat ra xa tam san dau");
    CHECK(orbInner.moveDy < -0.6f, "moveDy van giu thanh phan tiep tuyen de di chuyen vong tron");

    // 5c. Nguoi choi o ben ngoai ban kinh (300u > 180u) -> Luc keo hut ve tam (-X)
    auto orbOuter = ctrl.ComputeOrbitalKiteVector(300.0f, 0.0f);
    CHECK(orbOuter.currentRadius > 250.0f, "Ban kinh ben ngoai > 250u");
    CHECK(orbOuter.moveDx < -0.3f, "moveDx phai am de keo nhan vat ve tam san dau");
    CHECK(orbOuter.moveDy < -0.6f, "moveDy van giu thanh phan tiep tuyen de di chuyen vong tron");

    // 5d. Che do quay nguoc chieu kim dong ho (Counter-Clockwise)
    auto ccwCfg = ctrl.Config();
    ccwCfg.clockwiseOrbit = false;
    ctrl.SetConfig(ccwCfg);
    auto orbCCW = ctrl.ComputeOrbitalKiteVector(180.0f, 0.0f);
    CHECK(std::abs(orbCCW.moveDy - 1.0f) < 0.05f, "moveDy phai huong +1 khi orbit nguoc chieu kim dong ho");

    // 6. Sub-test 6: Tich hop ComboManager (Auto-suppress Heavy Finisher va khoi phuc sau pha bat tu)
    KMBoxNet kmbox;
    kmbox.EnableEmulationMode(true);
    kmbox.SetIgnoreWindowFocus(true);

    ComboConfig ccfg{};
    ccfg.enabled = true;
    ccfg.bossStaggerBurstEnabled = true;
    ccfg.autoStaggerFinisher = true;
    ccfg.finisherSkillVk = 'R';
    ccfg.burstWindowDurationMs = 3500;

    ComboManager combo(ccfg);

    TelemetryPacket combatPkt{};
    combatPkt.player.posX = 100.0f;
    combatPkt.player.posY = 100.0f;
    combatPkt.player.currentHP = 1000;
    combatPkt.player.maxHP = 1000;
    combatPkt.entityCount = 1;

    auto& cBoss = combatPkt.entities[0];
    cBoss.id = 777;
    cBoss.type = 1;
    cBoss.rarity = 3;
    cBoss.posX = 160.0f;
    cBoss.posY = 100.0f;
    cBoss.currentHP = 50000;
    cBoss.maxHP = 50000;
    cBoss.staggerProgress = 10000;
    cBoss.extraFlags = game_layout::kEntityFlagBoss | game_layout::kEntityFlagTargetable | game_layout::kEntityFlagStaggered;

    // Tick 1: Boss bi Stagger va de ton thuong -> Kich hoat Burst DPS Window
    combo.Update(combatPkt, kmbox, 50000);
    CHECK(combo.IsBossBurstWindow(), "ComboManager phai bat isBossBurstWindow khi Boss stagger o ActiveCombat");
    CHECK(combo.GetConfig().autoStaggerFinisher, "autoStaggerFinisher phai duoc bat");

    // Tick 2: Boss chuyen phase bat tu (Immunity) -> ComboManager ngat ngay lap tuc Heavy Finisher va Burst Window!
    cBoss.extraFlags |= game_layout::kEntityFlagInvulnerable;
    combo.Update(combatPkt, kmbox, 50100);
    CHECK(combo.GetBossPhaseController().IsBossInvulnerable(), "BossPhaseController trong ComboManager phai bao Invulnerable");
    CHECK(!combo.IsBossBurstWindow(), "isBossBurstWindow phai lap tuc bi tat khi Boss bat tu");
    CHECK(!combo.GetConfig().autoStaggerFinisher, "autoStaggerFinisher phai bi ngat de tranh phi skill finisher");

    // Tick 3: Boss het pha bat tu tro lai ActiveCombat -> Tu dong phuc hoi autoStaggerFinisher
    cBoss.extraFlags &= ~game_layout::kEntityFlagInvulnerable;
    combo.Update(combatPkt, kmbox, 50500);
    CHECK(!combo.GetBossPhaseController().IsBossInvulnerable(), "BossPhaseController phai bao het Invulnerable");
    CHECK(combo.GetConfig().autoStaggerFinisher, "autoStaggerFinisher phai tu dong phuc hoi khi Boss vulnerable tro lai");

    // 7. Sub-test 7: Benchmark 100,000 iterations do do tre Hot Path DOD (< 0.05 us)
    constexpr int kBenchIters = 100000;
    combat::BossPhaseController benchCtrl(config);

    std::streambuf* prevBuf = std::cout.rdbuf(nullptr);

    // Benchmark UpdatePhase
    auto tP0 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < kBenchIters; ++i) {
        benchCtrl.UpdatePhase(combatPkt, 777, 60000 + i);
    }
    auto tP1 = std::chrono::high_resolution_clock::now();

    // Benchmark ComputeOrbitalKiteVector
    auto tO0 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < kBenchIters; ++i) {
        auto vec = benchCtrl.ComputeOrbitalKiteVector(150.0f + static_cast<float>(i % 50), 120.0f);
        (void)vec;
    }
    auto tO1 = std::chrono::high_resolution_clock::now();
    std::cout.rdbuf(prevBuf);

    const double phaseTotalUs = std::chrono::duration<double, std::micro>(tP1 - tP0).count();
    const double phaseAvgUs = phaseTotalUs / static_cast<double>(kBenchIters);

    const double orbTotalUs = std::chrono::duration<double, std::micro>(tO1 - tO0).count();
    const double orbAvgUs = orbTotalUs / static_cast<double>(kBenchIters);

    CHECK(phaseAvgUs < 0.05, "Do tre UpdatePhase phai < 0.05 us. Thuc te: " + std::to_string(phaseAvgUs) + " us");
    CHECK(orbAvgUs < 0.05, "Do tre ComputeOrbitalKiteVector phai < 0.05 us. Thuc te: " + std::to_string(orbAvgUs) + " us");

    std::cout << "  -> Boss Invulnerability & Phase Detection (Test 101) OK: "
              << kBenchIters << " iters, phase update latency = " << phaseAvgUs
              << " us/call, orbital kiting latency = " << orbAvgUs << " us/call (< 0.05 us threshold)"
              << std::endl;
}


