ADPT Sim Inventory Support

v30.18: Pentagon build radar + armor target selector with PDA storage

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         ADPT Sim Inventory Support
// @namespace    http://tampermonkey.net/
// @version      30.18
// @description  v30.18: Pentagon build radar + armor target selector with PDA storage
// @author       csv construct & AI Update
// @match        https://www.torn.com/page.php?sid=ItemMarket*
// @match        https://www.torn.com/amarket.php*
// @match        https://www.torn.com/item.php*
// @match        https://www.torn.com/factions.php?step=your&type=5*
// @license      me
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest

// ==/UserScript==

(function() {
    'use strict';

    // ── DEBUG FLAG ── set true to enable console debug output ──────────────
    const DEBUG = false;
    const dbg = (...args) => { if (DEBUG) console.log(...args); };
    // ───────────────────────────────────────────────────────────────────────

    // ==========================================
    // PDA & STORAGE WRAPPERS
    // ==========================================
    var rD_xmlhttpRequest;
    var rD_setValue;
    var rD_getValue;
    var rD_listValues;
    var rD_deleteValue;
    var rD_registerMenuCommand;
    var rD_PDA;
    var apikey = "###PDA-APIKEY###";
    if (apikey[0] !== "#") {
        rD_PDA = true;
        rD_xmlhttpRequest = function (details) {
            if (details.method.toLowerCase() === "get") {
                return PDA_httpGet(details.url).then(details.onload).catch(details.onerror || ((e) => console.error("[ADPT] Request Error:", e)));
            } else if (details.method.toLowerCase() === "post") {
                return PDA_httpPost(details.url, details.headers || {}, details.body || details.data || "").then(details.onload).catch(details.onerror || ((e) => console.error("[ADPT] Request Error:", e)));
            }
        };
        rD_setValue = (name, value) => localStorage.setItem(name, JSON.stringify(value));
        rD_getValue = (name, defaultValue) => { try { const v = localStorage.getItem(name); return v !== null ? JSON.parse(v) : defaultValue; } catch(e) { return defaultValue; } };
        rD_listValues = () => Object.keys(localStorage);
        rD_deleteValue = (name) => localStorage.removeItem(name);
        rD_registerMenuCommand = () => { /* Disabled on PDA */ };
    } else {
        rD_PDA = false;
        rD_xmlhttpRequest = (typeof GM_xmlhttpRequest !== 'undefined') ? GM_xmlhttpRequest : () => {};
        rD_setValue = (typeof GM_setValue !== 'undefined') ? GM_setValue : (k,v) => localStorage.setItem(k, JSON.stringify(v));
        rD_getValue = (typeof GM_getValue !== 'undefined') ? GM_getValue : (k,d) => { try { const v = localStorage.getItem(k); return v !== null ? JSON.parse(v) : d; } catch(e) { return d; } };
        rD_listValues = (typeof GM_listValues !== 'undefined') ? GM_listValues : () => Object.keys(localStorage);
        rD_deleteValue = (typeof GM_deleteValue !== 'undefined') ? GM_deleteValue : (k) => localStorage.removeItem(k);
        rD_registerMenuCommand = (typeof GM_registerMenuCommand !== 'undefined') ? GM_registerMenuCommand : () => {};
    }
    const adptStorage = {
        set: (k, v) => rD_setValue('adpt_' + k, v),
        get: (k, d) => rD_getValue('adpt_' + k, d)
    };

    // --- CONFIG STATS ---
    const MY_STATS = { str: 1000000000, spd: 1000000000 };
    const ENEMY_STATS = { def: 1000000000, dex: 1000000000 };
    const SIM_LIMIT =25;
    const ARMOR_BASE = 40;

    const JAPANESE_WEAPONS = [
        "Dual Samurai Swords", "Kama", "Katana", "Kodachi", "Sai", "Samurai Sword", "Yasukuni Sword"
    ];

    const WEAPON_DB = {
        "9mm Uzi": { "mag": 30, "rof": 20.0 }, "AK-47": { "mag": 30, "rof": 6.0 }, "AK74U": { "mag": 30, "rof": 5.0 },
        "ArmaLite M-15A4": { "mag": 15, "rof": 4.0 }, "Benelli M1 Tactical": { "mag": 7, "rof": 2.5 }, "Benelli M4 Super": { "mag": 7, "rof": 2.5 },
        "Bushmaster Carbon 15": { "mag": 30, "rof": 15.0 }, "Dual Bushmasters": { "mag": 60, "rof": 17.0 }, "Dual MP5s": { "mag": 60, "rof": 13.0 },
        "Dual P90s": { "mag": 100, "rof": 15.0 }, "Dual TMPs": { "mag": 30, "rof": 20.0 }, "Dual Uzis": { "mag": 60, "rof": 20.0 },
        "Egg Propelled Launcher": { "mag": 1000, "rof": 405.0 }, "Enfield SA-80": { "mag": 30, "rof": 3.5 }, "Gold Plated AK-47": { "mag": 45, "rof": 5.0 },
        "Heckler & Koch SL8": { "mag": 10, "rof": 5.5 }, "Ithaca 37": { "mag": 4, "rof": 2.5 }, "Jackhammer": { "mag": 10, "rof": 7.5 },
        "M16 A2 Rifle": { "mag": 30, "rof": 7.0 }, "M249 SAW": { "mag": 100, "rof": 20.0 }, "M4A1 Colt Carbine": { "mag": 30, "rof": 6.5 },
        "Mag 7": { "mag": 5, "rof": 3.0 }, "Minigun": { "mag": 200, "rof": 25.0 }, "MP 40": { "mag": 32, "rof": 4.0 }, "MP5 Navy": { "mag": 30, "rof": 6.5 },
        "Negev NG-5": { "mag": 100, "rof": 20.0 }, "Neutrilux 2000": { "mag": 1000, "rof": 383.0 }, "Nock Gun": { "mag": 7, "rof": 7.0 }, "P90": { "mag": 50, "rof": 20.0 },
        "PKM": { "mag": 50, "rof": 13.0 }, "Prototype": { "mag": 100, "rof": 25.0 }, "Rheinmetall MG 3": { "mag": 100, "rof": 25.0 },
        "Sawed-Off Shotgun": { "mag": 2, "rof": 1.5 }, "SIG 550": { "mag": 20, "rof": 5.5 }, "SIG 552": { "mag": 30, "rof": 5.5 },
        "SKS Carbine": { "mag": 10, "rof": 4.5 }, "Snow Cannon": { "mag": 950, "rof": 6.5 }, "Steyr AUG": { "mag": 30, "rof": 6.5 },
        "Stoner 96": { "mag": 100, "rof": 11.5 }, "Tavor TAR-21": { "mag": 30, "rof": 6.0 }, "Thompson": { "mag": 20, "rof": 4.0 },
        "Vektor CR-21": { "mag": 30, "rof": 6.0 }, "XM8 Rifle": { "mag": 30, "rof": 6.0 }, "Beretta 92FS": { "mag": 20, "rof": 4.5 },
        "Beretta M9": { "mag": 17, "rof": 4.5 }, "Beretta Pico": { "mag": 6, "rof": 1.5 }, "Blowgun": { "mag": 1, "rof": 1.0 },
        "Blunderbuss": { "mag": 1, "rof": 1.0 }, "BT MP9": { "mag": 30, "rof": 13.5 }, "China Lake": { "mag": 3, "rof": 1.0 },
        "Cobra Derringer": { "mag": 2, "rof": 1.5 }, "Crossbow": { "mag": 1, "rof": 1.0 }, "Desert Eagle": { "mag": 7, "rof": 2.5 },
        "Dual 92G Berettas": { "mag": 46, "rof": 10.0 }, "Fiveseven": { "mag": 20, "rof": 6.5 }, "Flamethrower": { "mag": 1, "rof": 1.0 },
        "Flare Gun": { "mag": 1, "rof": 1.0 }, "Glock 17": { "mag": 20, "rof": 4.5 }, "Harpoon": { "mag": 1, "rof": 1.0 },
        "Homemade Pocket Shotgun": { "mag": 1, "rof": 1.0 }, "Lorcin 380": { "mag": 6, "rof": 3.0 }, "Luger": { "mag": 8, "rof": 2.0 },
        "Magnum": { "mag": 6, "rof": 1.5 }, "Milkor MGL": { "mag": 6, "rof": 1.0 }, "MP5k": { "mag": 15, "rof": 6.0 },
        "Pink Mac-10": { "mag": 32, "rof": 12.0 }, "Qsz-92": { "mag": 15, "rof": 4.0 }, "Raven MP25": { "mag": 6, "rof": 4.0 },
        "RPG Launcher": { "mag": 1, "rof": 1.0 }, "SMAW Launcher": { "mag": 1, "rof": 1.0 }, "Ruger 57": { "mag": 20, "rof": 3.5 },
        "S&W M29": { "mag": 6, "rof": 1.5 }, "S&W Revolver": { "mag": 6, "rof": 1.5 }, "Skorpion": { "mag": 20, "rof": 9.0 },
        "Slingshot": { "mag": 1, "rof": 1.0 }, "Springfield 1911": { "mag": 7, "rof": 3.0 }, "Taser": { "mag": 1, "rof": 1.0 },
        "Taurus": { "mag": 5, "rof": 2.0 }, "TMP": { "mag": 30, "rof": 15.0 }, "Tranquilizer Gun": { "mag": 1, "rof": 1.0 },
        "USP": { "mag": 12, "rof": 4.5 }, "Macana": { "mag": 0, "rof": 1.0 }, "Diamond Bladed Knife": { "mag": 0, "rof": 1.0 },
        "Kodachi": { "mag": 0, "rof": 1.0 }, "Katana": { "mag": 0, "rof": 1.0 }, "Naval Cutlass": { "mag": 0, "rof": 1.0 },
        "Butterfly Knife": { "mag": 0, "rof": 1.0 }, "Kitchen Knife": { "mag": 0, "rof": 1.0 }, "Hammer": { "mag": 0, "rof": 1.0 },
        "Crowbar": { "mag": 0, "rof": 1.0 }, "Yasukuni Sword": { "mag": 0, "rof": 1.0 }, "Sledgehammer": { "mag": 0, "rof": 1.0 },
        "Claymore Sword": { "mag": 0, "rof": 1.0 }, "Sai": { "mag": 0, "rof": 1.0 }, "Kama": { "mag": 0, "rof": 1.0 }, "Dual Samurai Swords": { "mag": 0, "rof": 1.0 }, "Samurai Sword": { "mag": 0, "rof": 1.0 },
        "Axe": { "mag": 0, "rof": 1.0 }, "Baseball Bat": { "mag": 0, "rof": 1.0 }, "Blood Spattered Sickle": { "mag": 0, "rof": 1.0 },
        "Bone Saw": { "mag": 0, "rof": 1.0 }, "Bo Staff": { "mag": 0, "rof": 1.0 }, "Bread Knife": { "mag": 0, "rof": 1.0 },
        "Bug Swatter": { "mag": 0, "rof": 1.0 }, "Cattle Prod": { "mag": 0, "rof": 1.0 }, "Chain Whip": { "mag": 0, "rof": 1.0 },
        "Chainsaw": { "mag": 0, "rof": 1.0 }, "Cleaver": { "mag": 0, "rof": 1.0 }, "Cricket Bat": { "mag": 0, "rof": 1.0 },
        "Dagger": { "mag": 0, "rof": 1.0 }, "Devil's Pitchfork": { "mag": 0, "rof": 1.0 }, "Diamond Icicle": { "mag": 0, "rof": 1.0 },
        "Dual Axes": { "mag": 0, "rof": 1.0 }, "Dual Hammers": { "mag": 0, "rof": 1.0 }, "Dual Scimitars": { "mag": 0, "rof": 1.0 },
        "Duke's Hammer": { "mag": 0, "rof": 1.0 }, "Fine Chisel": { "mag": 0, "rof": 1.0 }, "Flail": { "mag": 0, "rof": 1.0 },
        "Frying Pan": { "mag": 0, "rof": 1.0 }, "Golden Broomstick": { "mag": 0, "rof": 1.0 }, "Golf Club": { "mag": 0, "rof": 1.0 },
        "Guandao": { "mag": 0, "rof": 1.0 }, "Handbag": { "mag": 0, "rof": 1.0 }, "Ice Pick": { "mag": 0, "rof": 1.0 },
        "Ivory Walking Cane": { "mag": 0, "rof": 1.0 }, "Knuckle Dusters": { "mag": 0, "rof": 1.0 }, "Lead Pipe": { "mag": 0, "rof": 1.0 },
        "Leather Bullwhip": { "mag": 0, "rof": 1.0 }, "Madball": { "mag": 0, "rof": 1.0 }, "Meat Hook": { "mag": 0, "rof": 1.0 },
        "Metal Nunchakus": { "mag": 0, "rof": 1.0 }, "Ninja Claws": { "mag": 0, "rof": 1.0 }, "Pair of High Heels": { "mag": 0, "rof": 1.0 },
        "Pair of Ice Skates": { "mag": 0, "rof": 1.0 }, "Pen Knife": { "mag": 0, "rof": 1.0 }, "Penelope": { "mag": 0, "rof": 1.0 },
        "Petrified Humerus": { "mag": 0, "rof": 1.0 }, "Pillow": { "mag": 0, "rof": 1.0 }, "Plastic Sword": { "mag": 0, "rof": 1.0 },
        "Poison Umbrella": { "mag": 0, "rof": 1.0 }, "Riding Crop": { "mag": 0, "rof": 1.0 }, "Rusty Sword": { "mag": 0, "rof": 1.0 },
        "Scalpel": { "mag": 0, "rof": 1.0 }, "Scimitar": { "mag": 0, "rof": 1.0 }, "Spear": { "mag": 0, "rof": 1.0 },
        "Swiss Army Knife": { "mag": 0, "rof": 1.0 }, "Twin Tiger Hooks": { "mag": 0, "rof": 1.0 }, "Wand of Destruction": { "mag": 0, "rof": 1.0 },
        "Wooden Nunchaku": { "mag": 0, "rof": 1.0 }, "Wushu Double Axes": { "mag": 0, "rof": 1.0 },
        "Type 98 Anti Tank": { "mag": 1, "rof": 1.0 }
    };

    const BONUS_DB = {
        "Japanese Blade Mastery": {"type": "Damage", "scope": "Permanent", "weight": 1.0},
        "Assassinate": {"type": "Damage", "scope": "Turn_1", "weight": 1.0},
        "Backstab": {"type": "Damage", "scope": "Turn_1", "weight": 1.0},
        "Blindside": {"type": "Damage", "scope": "Turn_1", "weight": 1.0},
        "Specialist": {"type": "Damage", "scope": "Mag_1", "weight": 1.0},
        "Powerful": {"type": "Damage", "scope": "Permanent", "weight": 1.0},
        "Puncture": {"type": "Damage", "scope": "Permanent", "weight": 1.0},
        "Piercing": {"type": "Damage", "scope": "Permanent", "weight": 1.0},
        "Eviscerate": {"type": "Damage", "scope": "Permanent", "weight": 1.0},
        "Empower": {"type": "Stat_Buff", "scope": "Permanent", "weight": 0.1},
        "Smurf": {"type": "Damage", "scope": "Permanent", "weight": 1.0},
        "Double Tap": {"type": "Multi-Hit", "scope": "Probabilistic", "weight": 1.0},
        "Fury": {"type": "Multi-Hit", "scope": "Probabilistic", "weight": 1.0},
        "Rage": {"type": "Multi-Hit", "scope": "Probabilistic", "weight": 4.0},
        "Double-edged": {"type": "Damage", "scope": "Probabilistic", "weight": 1.0},
        "Deadly": {"type": "Crit", "scope": "Permanent", "weight": 1.0},
        "Expose": {"type": "Crit", "scope": "Permanent", "weight": 1.0},
        "Sure Shot": {"type": "Accuracy", "scope": "Permanent", "weight": 1.0},
        "Quicken": {"type": "Accuracy", "scope": "Permanent", "weight": 0.1},
        "Focus": {"type": "HitChance", "scope": "Scaling", "weight": 1.0},
        "Grace": {"type": "Mixed", "scope": "Permanent", "weight": 1.0},
        "Berserk": {"type": "Mixed", "scope": "Permanent", "weight": 1.0},
        "Weaken": {"type": "Stat_Debuff", "scope": "Scaling", "weight": 0.25},
        "Wither": {"type": "Stat_Debuff", "scope": "Scaling", "weight": 0.25},
        "Slow": {"type": "Stat_Debuff", "scope": "Scaling", "weight": 0.25},
        "Cripple": {"type": "Stat_Debuff", "scope": "Scaling", "weight": 0.25},
        "Freeze": {"type": "Stat_Debuff", "scope": "Scaling", "weight": 0.3},
        "Motivation": {"type": "Stat_Buff", "scope": "Scaling", "weight": 0.1},
        "Inspiration": {"type": "Stat_Buff", "scope": "Scaling", "weight": 0.2},
        "Frenzy": {"type": "Stat_Buff", "scope": "Scaling", "weight": 1.0},
        "Shred": {"type": "Damage_Taken", "scope": "Scaling", "weight": 1.0},
        "Wind-up": {"type": "Damage", "scope": "Special_Cycle", "weight": 1.0},
        "Bleed": {"type": "DOT", "scope": "DOT", "weight": 1.0},
        "Poison": {"type": "DOT", "scope": "DOT", "weight": 1.0},
        "Burning": {"type": "DOT", "scope": "DOT", "weight": 1.0},
        "Radiation": {"type": "DOT", "scope": "DOT", "weight": 1.0},
        "Irradiate": {"type": "DOT", "scope": "DOT", "weight": 1.0},
        "Throttle": {"type": "HitLoc", "scope": "Permanent", "weight": 1.0},
        "Deadeye": {"type": "HitLoc", "scope": "Permanent", "weight": 1.0},
        "Achilles": {"type": "HitLoc", "scope": "Permanent", "weight": 1.0},
        "Cupid": {"type": "HitLoc", "scope": "Permanent", "weight": 1.0},
        "Crusher": {"type": "HitLoc", "scope": "Permanent", "weight": 1.0},
        "Roshambo": {"type": "HitLoc", "scope": "Permanent", "weight": 1.0},
        "Execute": {"type": "Finisher", "scope": "Threshold", "weight": 1.0},
        "Execution": {"type": "Finisher", "scope": "Threshold", "weight": 1.0},
        "Comeback": {"type": "Damage", "scope": "Permanent", "weight": 0.5},
        "Penetrate": {"type": "Armor", "scope": "Permanent", "weight": 1.0},
        "Finale": {"type": "Damage", "scope": "Permanent", "weight": 0.1},
        "Conserve": {"type": "Utility", "scope": "Ammo_Eff", "weight": 1.0}
    };

    const RAW_HIT_LOCS = [
        {n: "Head",    w: 9.6,  m: 3.5, b: ["Deadeye", "Crusher"], isCrit: true},
        {n: "Throat",  w: 1.2,  m: 3.5, b: ["Throttle"],           isCrit: true},
        {n: "Heart",   w: 1.2,  m: 3.5, b: ["Cupid"],              isCrit: true},
        {n: "Chest",   w: 22.0, m: 2.0, b: [],                     isCrit: false},
        {n: "Stomach", w: 17.6, m: 2.0, b: [],                     isCrit: false},
        {n: "Groin",   w: 4.4,  m: 2.0, b: ["Roshambo"],           isCrit: false},
        {n: "Arms",    w: 8.8,  m: 1.0, b: [],                     isCrit: false},
        {n: "Hand",    w: 8.8,  m: 0.7, b: [],                     isCrit: false},
        {n: "Legs",    w: 17.6, m: 1.0, b: ["Achilles"],           isCrit: false},
        {n: "Foot",    w: 8.8,  m: 0.7, b: ["Achilles"],           isCrit: false}
    ];

    function calculateAvgHitMultiplier(activeBonuses) {
        let locs = JSON.parse(JSON.stringify(RAW_HIT_LOCS));

        activeBonuses.forEach(bonus => {
            const bN = bonus.name;
            const p = bonus.val / 100;
            const bData = BONUS_DB[bN];

            if (bN === "Expose") {
                let shiftChance = p * 100;
                let totalNonCritW = locs.reduce((sum, l) => sum + (l.isCrit ? 0 : l.w), 0);
                if (totalNonCritW > 0) {
                    locs.forEach(l => {
                        if (!l.isCrit) {
                            l.w -= (l.w / totalNonCritW) * shiftChance;
                            if (l.w < 0) l.w = 0;
                        } else {
                            let baseCritTotal = 12.0;
                            l.w += (l.w / baseCritTotal) * shiftChance;
                        }
                    });
                }
            }

            if (bData && bData.type === "HitLoc") {
                locs.forEach(l => {
                    if (l.b.includes(bN)) {
                        l.m *= (1 + p);
                    }
                });
            }
        });

        let totalW = 0, weightedSum = 0;
        locs.forEach(l => {
            weightedSum += (l.w * l.m);
            totalW += l.w;
        });
        return weightedSum / totalW;
    }

    function getHitChance(userSpeed, enemyDex, accuracy) {
        let effectiveSpeed = userSpeed * (accuracy / 50);
        let ratio = effectiveSpeed / enemyDex;
        let baseHitChance;
        if (ratio > 64) baseHitChance = 100;
        else if (ratio >= 1 && ratio <= 64) baseHitChance = 100 - 50 / 7 * (8 * Math.sqrt(1 / ratio) - 1);
        else if (ratio > 1 / 64 && ratio < 1) baseHitChance = 50 / 7 * (8 * Math.sqrt(ratio) - 1);
        else baseHitChance = 0;
        return baseHitChance / 100.0;
    }

    // Returns { armorVal, dexMult } for the currently saved armor selection,
    // accounting for weapon type (melee vs gun).
    // Used by simulateADPT so all injected ADPT scores reflect selected armor.
    function getActiveArmorForSim(weaponName) {
        const ADPT_ARMOR_TABLE = {
            combat:   { armorMelee: 40, armorGun: 40, dexMult: 1.0 },
            riot:     { armorMelee: 58.5, armorGun: 45, dexMult: 1.0 },
            assault:  { armorMelee: 45, armorGun: 58.5, dexMult: 1.0 },
            vanguard: { armorMelee: 50, armorGun: 50, dexMult: 2.5 }
        };
        const savedId = rD_getValue('adpt_target_armor', 'combat');
        const opt = ADPT_ARMOR_TABLE[savedId] || ADPT_ARMOR_TABLE.combat;
        const wEntry = WEAPON_DB[weaponName];
        const isMeleeWeapon = wEntry != null ? wEntry.mag === 0 : false;
        return {
            armorVal: isMeleeWeapon ? opt.armorMelee : opt.armorGun,
            dexMult:  opt.dexMult
        };
    }

    function simulateADPT(name, baseDmg, baseAcc, activeBonuses, ammoType = "") {
        const w = WEAPON_DB[name] || { mag: 0, rof: 1.0 };
        let conserveBonus = activeBonuses.find(b => b.name === "Conserve");
        let effMag = w.mag;
        if (conserveBonus) { effMag = w.mag / (1 - (conserveBonus.val / 100)); }

        if (ammoType === "Tracer") baseAcc += 10.0;

        let totalDmg = 0, bulletsSpent = 0, stacks = 0;
        let frenzyStacks = 0, focusStacks = 0, motivationStacks = 0;
        let currentMag = effMag;

        const locAvgM = calculateAvgHitMultiplier(activeBonuses);
        let history = [];

        const hasSpecialist = activeBonuses.some(b => b.name === "Specialist");

        // --- NEW AMMO LIMIT LOGIC ---
        // Determines maximum number of magazines allowed (1 if Specialist, else 3)
        const maxMags = hasSpecialist ? 1 : 3;
        // Tracks how many magazines have been loaded (starting with 1 initially)
        let magsUsed = 1;

        // Use selected armor for this simulation
        const _activeArmor = getActiveArmorForSim(name);

        for (let t = 1; t <= SIM_LIMIT; t++) {
            let currStr = MY_STATS.str, currSpd = MY_STATS.spd;
            let currDef = ENEMY_STATS.def, currDex = ENEMY_STATS.dex * _activeArmor.dexMult;

            // --- RELOAD LOGIC UPDATE ---
            if (w.mag > 0 && currentMag <= 0) {
                if (magsUsed >= maxMags) {
                    // Out of total magazines, deal 0 damage for the rest of the sim
                    history.push(0);
                    continue;
                } else {
                    // Reload weapon: consumes a turn, no damage done, increment magsUsed
                    magsUsed++;
                    currentMag = effMag;
                    history.push(0);
                    continue;
                }
            }

            let tM = 1.0, arm = _activeArmor.armorVal, acc = baseAcc;

            // Ammo type modifiers — matched to chart: HP=baseDmg*1.5 arm*1.5, IN=baseDmg*1.4 arm*1.1, PI=arm*0.5
            let ammoDmgMult = 1.0;
            if (ammoType === "Hollow point") { ammoDmgMult = 1.50; arm *= 1.50; }
            else if (ammoType === "Incendiary") { ammoDmgMult = 1.40; arm *= 1.10; }
            else if (ammoType === "Piercing") { arm *= 0.50; }

            let deadlyChance = 0, ammoUsageMult = 1.0;

            activeBonuses.forEach(bonus => {
                const bN = bonus.name;
                const p = bonus.val / 100;
                const b = BONUS_DB[bN];
                if (b) {
                    switch (b.scope) {
                        case "Turn_1": if (t === 1) tM *= (1 + p * b.weight); break;
                        case "Mag_1": if (bulletsSpent < effMag) tM *= (1 + p * b.weight); break;
                        case "Probabilistic":
                            tM *= (1 + p * b.weight);
                            if (b.type === "Multi-Hit") ammoUsageMult += p * b.weight;
                            break;
                        case "DOT": tM *= (1 + p * b.weight); break;
                        case "Threshold": if (p < 0.99) tM *= (1 / (1 - p)); break;
                        case "Special_Cycle": if (t % 2 !== 0) tM = 0; else tM *= (1 + p); break;
                        case "Scaling":
                            if (b.type === "Stat_Debuff") {
                                stacks = Math.min(3.0, stacks + p);
                                if (bN === "Weaken") currDef *= (1 - (stacks * 0.25));
                                else if (bN === "Cripple") currDex *= (1 - (stacks * 0.25));
                            } else if (b.type === "Stat_Buff") {
                                if (bN === "Motivation") { motivationStacks = Math.min(5, motivationStacks + p); currStr *= (1 + motivationStacks * 0.1); currSpd *= (1 + motivationStacks * 0.1); }
                                else if (bN === "Inspiration") { motivationStacks = Math.min(10, motivationStacks + p); currStr *= (1 + motivationStacks * b.weight); currSpd *= (1 + motivationStacks * b.weight); }
                                else if (bN === "Frenzy") {
                                    tM *= (1 + frenzyStacks * p);
                                    acc *= (1 + frenzyStacks * p);
                                }
                            } else {
                                if (bN === "Focus") {
                                } else {
                                    stacks = Math.min(10, stacks + 1);
                                    if (b.type === "Accuracy") acc *= (1 + stacks * p);
                                    else if (b.type === "Damage_Taken") tM *= (1 + stacks * p);
                                }
                            }
                            break;
                        case "Permanent":
                            if (bN === "Deadly") deadlyChance = p;
                            if (b.type === "Armor" || bN === "Puncture") arm *= (1 - p);
                            if (bN === "Quicken") currSpd *= (1 + p);
                            else if (bN === "Empower") currStr *= (1 + p);
                            else if (b.type === "Accuracy") acc *= (1 + p * b.weight);
                            else if (b.type === "Damage" && bN !== "Puncture") tM *= (1 + p * b.weight);
                            if (b.type === "Mixed") { if (bN === "Berserk") { tM *= (1 + p); acc *= (1 - p/2); } if (bN === "Grace") { acc *= (1 + p); tM *= (1 - p/2); } }
                            break;
                    }
                }
            });

            let probHit = getHitChance(currSpd, currDex, acc);

            let focusBonus = activeBonuses.find(b => b.name === "Focus");
            if (focusBonus) {
                let focusP = focusBonus.val / 100;
                probHit = Math.min(1.0, probHit + (focusStacks * focusP));
            }

            let turnDmg = 0;
            if (tM > 0) {
                let finalDamageMult = locAvgM;
                if (deadlyChance > 0) finalDamageMult = (deadlyChance * 6.0) + ((1 - deadlyChance) * locAvgM);
                let statDmgFactor = (currDef > 0) ? (currStr / (currStr + currDef)) / (MY_STATS.str / (MY_STATS.str + ENEMY_STATS.def)) : 1.0;
                turnDmg = (baseDmg * ammoDmgMult * (1 - arm/100) * finalDamageMult * tM * probHit * statDmgFactor);
                totalDmg += turnDmg;
            }

            history.push(turnDmg * 2);

            if (activeBonuses.some(b => b.name === "Frenzy")) frenzyStacks = Math.min(20, probHit * (frenzyStacks + 1));
            if (focusBonus) focusStacks = Math.min(20, (1 - probHit) * (focusStacks + 1));

            if (w.mag > 0) { let shots = w.rof * ammoUsageMult; currentMag -= shots; bulletsSpent += shots; }
        }

        return {
            score: (2 * totalDmg / SIM_LIMIT).toFixed(1),
            history: history
        };
    }


    // Simulate ADPT with custom stat overrides (for pentagon build comparison & armor)
    // statOverride: { str, spd, def, dex } — absolute values in same scale as MY_STATS
    // armorOverride: armor % value (numeric), null = use ARMOR_BASE
    function simulateADPTCustom(name, baseDmg, baseAcc, activeBonuses, ammoType, statOverride, armorOverride) {
        const w = WEAPON_DB[name] || { mag: 0, rof: 1.0 };
        let conserveBonus = activeBonuses.find(b => b.name === "Conserve");
        let effMag = w.mag;
        if (conserveBonus) { effMag = w.mag / (1 - (conserveBonus.val / 100)); }
        if (ammoType === "Tracer") baseAcc += 10.0;

        let totalDmg = 0, bulletsSpent = 0, stacks = 0;
        let frenzyStacks = 0, focusStacks = 0, motivationStacks = 0;
        let currentMag = effMag;
        const locAvgM = calculateAvgHitMultiplier(activeBonuses);
        const hasSpecialist = activeBonuses.some(b => b.name === "Specialist");
        const maxMags = hasSpecialist ? 1 : 3;
        let magsUsed = 1;
        let history_c = [];

        const baseStr = (statOverride && statOverride.str != null) ? statOverride.str : MY_STATS.str;
        const baseSpd = (statOverride && statOverride.spd != null) ? statOverride.spd : MY_STATS.spd;
        const baseDef = (statOverride && statOverride.def != null) ? statOverride.def : ENEMY_STATS.def;
        const baseDex = (statOverride && statOverride.dex != null) ? statOverride.dex : ENEMY_STATS.dex;

        // Reference ratio for normalization (always the global baseline)
        const refRatio = MY_STATS.str / (MY_STATS.str + ENEMY_STATS.def); // = 0.5

        for (let t = 1; t <= SIM_LIMIT; t++) {
            let currStr = baseStr, currSpd = baseSpd;
            let currDef = baseDef, currDex = baseDex;

            if (w.mag > 0 && currentMag <= 0) {
                if (magsUsed >= maxMags) { history_c.push(0); continue; }
                else { magsUsed++; currentMag = effMag; history_c.push(0); continue; }
            }

            let tM = 1.0;
            let arm = (armorOverride != null) ? armorOverride : ARMOR_BASE;
            let acc = baseAcc;
            let ammoDmgMult = 1.0;
            if (ammoType === "Hollow point") { ammoDmgMult = 1.50; arm *= 1.50; }
            else if (ammoType === "Incendiary") { ammoDmgMult = 1.40; arm *= 1.10; }
            else if (ammoType === "Piercing") { arm *= 0.50; }

            let deadlyChance = 0, ammoUsageMult = 1.0;
            activeBonuses.forEach(bonus => {
                const bN = bonus.name; const p = bonus.val / 100; const b = BONUS_DB[bN];
                if (b) {
                    switch (b.scope) {
                        case "Turn_1": if (t === 1) tM *= (1 + p * b.weight); break;
                        case "Mag_1": if (bulletsSpent < effMag) tM *= (1 + p * b.weight); break;
                        case "Probabilistic": tM *= (1 + p * b.weight); if (b.type === "Multi-Hit") ammoUsageMult += p * b.weight; break;
                        case "DOT": tM *= (1 + p * b.weight); break;
                        case "Threshold": if (p < 0.99) tM *= (1 / (1 - p)); break;
                        case "Special_Cycle": if (t % 2 !== 0) tM = 0; else tM *= (1 + p); break;
                        case "Scaling":
                            if (b.type === "Stat_Debuff") {
                                stacks = Math.min(3.0, stacks + p);
                                if (bN === "Weaken") currDef *= (1 - (stacks * 0.25));
                                else if (bN === "Cripple") currDex *= (1 - (stacks * 0.25));
                            } else if (b.type === "Stat_Buff") {
                                if (bN === "Motivation") { motivationStacks = Math.min(5, motivationStacks + p); currStr *= (1 + motivationStacks * 0.1); currSpd *= (1 + motivationStacks * 0.1); }
                                else if (bN === "Inspiration") { motivationStacks = Math.min(10, motivationStacks + p); currStr *= (1 + motivationStacks * b.weight); currSpd *= (1 + motivationStacks * b.weight); }
                                else if (bN === "Frenzy") { tM *= (1 + frenzyStacks * p); acc *= (1 + frenzyStacks * p); }
                            } else {
                                if (bN !== "Focus") { stacks = Math.min(10, stacks + 1); if (b.type === "Accuracy") acc *= (1 + stacks * p); else if (b.type === "Damage_Taken") tM *= (1 + stacks * p); }
                            }
                            break;
                        case "Permanent":
                            if (bN === "Deadly") deadlyChance = p;
                            if (b.type === "Armor" || bN === "Puncture") arm *= (1 - p);
                            if (bN === "Quicken") currSpd *= (1 + p);
                            else if (bN === "Empower") currStr *= (1 + p);
                            else if (b.type === "Accuracy") acc *= (1 + p * b.weight);
                            else if (b.type === "Damage" && bN !== "Puncture") tM *= (1 + p * b.weight);
                            if (b.type === "Mixed") { if (bN === "Berserk") { tM *= (1 + p); acc *= (1 - p/2); } if (bN === "Grace") { acc *= (1 + p); tM *= (1 - p/2); } }
                            break;
                    }
                }
            });

            let probHit = getHitChance(currSpd, currDex, acc);
            let focusBonus = activeBonuses.find(b => b.name === "Focus");
            if (focusBonus) { let focusP = focusBonus.val / 100; probHit = Math.min(1.0, probHit + (focusStacks * focusP)); }

            let turnDmg = 0;
            if (tM > 0) {
                let finalDamageMult = locAvgM;
                if (deadlyChance > 0) finalDamageMult = (deadlyChance * 6.0) + ((1 - deadlyChance) * locAvgM);
                // statDmgFactor: ratio of (our str vs their def) normalised to global baseline
                let statDmgFactor = (currDef > 0) ? (currStr / (currStr + currDef)) / refRatio : 1.0;
                turnDmg = (baseDmg * ammoDmgMult * (1 - arm/100) * finalDamageMult * tM * probHit * statDmgFactor);
                totalDmg += turnDmg;
            }

            history_c.push(turnDmg * 2);
            if (activeBonuses.some(b => b.name === "Frenzy")) frenzyStacks = Math.min(20, probHit * (frenzyStacks + 1));
            if (focusBonus) focusStacks = Math.min(20, (1 - probHit) * (focusStacks + 1));
            if (w.mag > 0) { let shots = w.rof * ammoUsageMult; currentMag -= shots; bulletsSpent += shots; }
        }
        return { score: parseFloat((2 * totalDmg / SIM_LIMIT).toFixed(1)), history: history_c };
    }

    // --- UI INJECTION FUNCTIONS ---
    function getADPTColor(v) {
        let val = Math.max(10, Math.min(v, 65));
        return `hsl(${(val - 10) / 55 * 180}, 100%, 50%)`;
    }

    function showGraphTooltip(e, history, weaponContext) {
        e.stopPropagation();
        e.preventDefault();
        let existing = document.getElementById('adpt-graph-tooltip');
        if (existing) existing.remove();

        const maxDmg = Math.max(...history, 1);
        const minDmg = Math.min(...history);

        // ── ARMOR OPTIONS (saved via PDA-compatible storage) ──────────────
        // effectiveArmorMelee / effectiveArmorGun:
        // Riot: base 50 + 30% melee reduction → melee sees (1-0.5)*0.7=0.35 dmg factor
        //       equivalent armor% = (1-0.35)*100 = 65% for melee; 50% for gun
        // Assault: same logic swapped for gun
        // Vanguard: 55% armor, no weapon-type reduction; +150% dex bonus handled separately
        const ARMOR_OPTIONS = [
            { id: 'combat',   label: 'Combat (40)',          armorMelee: 40, armorGun: 40, dexMult: 1.0 },
            { id: 'riot',     label: 'Riot (45) -30% melee', armorMelee: 58.5, armorGun: 45, dexMult: 1.0 },
            { id: 'assault',  label: 'Assault (45) -30% gun',armorMelee: 45, armorGun: 58.5, dexMult: 1.0 },
            { id: 'vanguard', label: 'Vanguard (50) +150%dex',armorMelee: 50, armorGun: 50, dexMult: 2.5 }
        ];
        let selectedArmorId = adptStorage.get('target_armor', 'combat');

        // ── Detect weapon type: use isMelee stored in weaponContext at inject time ──
        // Computed once from WEAPON_DB when the item was scanned; unknown = false (gun).
        const isMelee = !!(weaponContext && weaponContext.isMelee);

        // ── DEBUG ─────────────────────────────────────────────────────────
        if (DEBUG && weaponContext) {
            const wdbEntry = WEAPON_DB[weaponContext.name];
            dbg(
                '%c[ADPT Debug]%c ' + weaponContext.name,
                'color:#4cf;font-weight:bold', 'color:#fff',
                '\n  Type     :', isMelee ? '⚔️  MELEE' : '🔫 RANGED',
                '\n  isMelee  :', isMelee,
                '\n  WEAPON_DB entry:', wdbEntry ? 'mag=' + wdbEntry.mag + ' rof=' + wdbEntry.rof : '❌ NOT FOUND (treated as ranged)',
                '\n  Armor    :', selectedArmorId,
                '\n  baseDmg  :', weaponContext.baseDmg,
                '\n  baseAcc  :', weaponContext.baseAcc,
                '\n  bonuses  :', weaponContext.activeBonuses.map(b => b.name + '(' + b.val + '%)').join(', ') || 'none'
            );
            const activeOpt = ARMOR_OPTIONS.find(o => o.id === selectedArmorId) || ARMOR_OPTIONS[0];
            dbg(
                '%c[ADPT Debug]%c Armor effect',
                'color:#fca;font-weight:bold', 'color:#fff',
                '\n  Selected armor :', activeOpt.label,
                '\n  Effective arm% :', isMelee ? activeOpt.armorMelee + '% (melee)' : activeOpt.armorGun + '% (gun)',
                '\n  Dmg factor     :', isMelee ? (1 - activeOpt.armorMelee/100).toFixed(3) : (1 - activeOpt.armorGun/100).toFixed(3),
                '\n  dexMult        :', activeOpt.dexMult
            );
        }
        // ── END DEBUG ─────────────────────────────────────────────────────

        // ── Compute ADPT for selected armor ───────────────────────────────
        // Armor simulation: use effective armor value that already encodes
        // Riot -30% melee / Assault -30% gun as equivalent armor percentage.
        // Vanguard dexMult scales enemy DEX (2.5x = base + 150%).
        function getArmorADPT(armorId) {
            if (!weaponContext) return null;
            const opt = ARMOR_OPTIONS.find(o => o.id === armorId) || ARMOR_OPTIONS[0];
            const armorVal = isMelee ? opt.armorMelee : opt.armorGun;
            const dexMod = ENEMY_STATS.dex * opt.dexMult;
            const statOverride = { str: MY_STATS.str, spd: MY_STATS.spd, def: ENEMY_STATS.def, dex: dexMod };
            const result = simulateADPTCustom(
                weaponContext.name, weaponContext.baseDmg, weaponContext.baseAcc,
                weaponContext.activeBonuses, weaponContext.ammoType || '',
                statOverride, armorVal
            );
            return { score: result.score.toFixed(1), history: result.history };
        }

        // ── Pentagon data: 5 build scenarios ─────────────────────────────
        // Builds: Normal (balanced), No Strength, No Speed, No Dex, No Def
        // "No X" means that stat is 1 (essentially zero contribution)
        // Pentagon: enemy stat allocations from a 100-point pool
        // Only def and dex affect the attacker's formulas.
        // Attacker is fixed at 25/25 str/spd. Enemy varies def/dex allocation.
        // P = pool unit = MY_STATS.str (1e9) represents 100 stat points
        // 25 pts = 0.25*P, 33.3 pts = 0.333*P
        function getBuildADPT(buildId) {
            if (!weaponContext) return 0;
            const P = MY_STATS.str; // = 1e9, represents 100 stat pts
            // Attacker always fixed at 25 pts str, 25 pts spd
            const atkStr = 0.25 * P;
            const atkSpd = 0.25 * P;
            let enemyDef, enemyDex;
            // Enemy distributes 100 pts; "no X" means that stat = 0, rest split equally
            switch (buildId) {
                case 'normal':
                    enemyDef = 0.25 * P; enemyDex = 0.25 * P; break;
                case 'no_str':   // enemy 0str 33spd 33def 33dex
                    enemyDef = (1/3) * P; enemyDex = (1/3) * P; break;
                case 'no_spd':   // enemy 33str 0spd 33def 33dex
                    enemyDef = (1/3) * P; enemyDex = (1/3) * P; break;
                case 'no_def':   // enemy 33str 33spd 0def 33dex
                    enemyDef = 0.001 * P; enemyDex = (1/3) * P; break;
                case 'no_dex':   // enemy 33str 33spd 33def 0dex
                    enemyDef = (1/3) * P; enemyDex = 0.001 * P; break;
                default:
                    enemyDef = 0.25 * P; enemyDex = 0.25 * P;
            }
            const activeOpt = ARMOR_OPTIONS.find(o => o.id === selectedArmorId) || ARMOR_OPTIONS[0];
            // Effective armor already encodes Riot/Assault weapon-type reduction
            const armorVal = isMelee ? activeOpt.armorMelee : activeOpt.armorGun;
            // Vanguard dexMult scales enemy dex on top of build's dex allocation
            const dexWithArmor = enemyDex * activeOpt.dexMult;
            const statOverrideWithArmor = { str: atkStr, spd: atkSpd, def: enemyDef, dex: dexWithArmor };
            return simulateADPTCustom(
                weaponContext.name, weaponContext.baseDmg, weaponContext.baseAcc,
                weaponContext.activeBonuses, weaponContext.ammoType || '',
                statOverrideWithArmor, armorVal
            ).score;
        }

        const pentagonBuilds = [
            { id: 'normal', label: 'Normal' },
            { id: 'no_str', label: 'No STR' },
            { id: 'no_spd', label: 'No SPD' },
            { id: 'no_dex', label: 'No DEX' },
            { id: 'no_def', label: 'No DEF' }
        ];

        // ── Build pentagon SVG ────────────────────────────────────────────
        function buildPentagonSVG(scores, maxVal) {
            const cx = 75, cy = 72, r = 55;
            const n = scores.length;
            const toXY = (i, frac) => {
                const angle = (Math.PI * 2 * i / n) - Math.PI / 2;
                return { x: cx + r * frac * Math.cos(angle), y: cy + r * frac * Math.sin(angle) };
            };
            // Grid lines
            let gridLines = '';
            [0.25, 0.5, 0.75, 1.0].forEach(frac => {
                const pts = scores.map((_, i) => { const p = toXY(i, frac); return `${p.x},${p.y}`; }).join(' ');
                gridLines += `<polygon points="${pts}" fill="none" stroke="#444" stroke-width="${frac === 1 ? 0.8 : 0.5}" opacity="0.7"/>`;
            });
            // Axis lines
            let axisLines = '';
            scores.forEach((_, i) => {
                const outer = toXY(i, 1.0);
                axisLines += `<line x1="${cx}" y1="${cy}" x2="${outer.x}" y2="${outer.y}" stroke="#555" stroke-width="0.5"/>`;
            });
            // Data polygon
            const dataFracs = scores.map(s => Math.max(0.05, s.score / maxVal));
            const dataPts = dataFracs.map((f, i) => { const p = toXY(i, f); return `${p.x},${p.y}`; }).join(' ');
            // Labels
            let labels = '';
            scores.forEach((s, i) => {
                const lp = toXY(i, 1.18);
                const labelColor = s.id === 'normal' ? '#7af' : '#fa7';
                labels += `<text x="${lp.x}" y="${lp.y}" text-anchor="middle" dominant-baseline="middle" font-size="7" fill="${labelColor}" font-weight="bold">${s.label}</text>`;
                // Score label: follow actual data point (may be beyond outer ring if >100)
                const dp = toXY(i, dataFracs[i]);
                // Scores >100 get a gold highlight to show they overflow the scale
                const valColor = s.score > maxVal ? '#ffd700' : '#fff';
                labels += `<text x="${dp.x}" y="${dp.y - 4}" text-anchor="middle" font-size="6" fill="${valColor}" font-weight="${s.score > maxVal ? 'bold' : 'normal'}" opacity="0.95">${s.score.toFixed(0)}</text>`;
            });
            return `<svg width="150" height="145" xmlns="http://www.w3.org/2000/svg">
                ${gridLines}${axisLines}
                <polygon points="${dataPts}" fill="rgba(100,200,255,0.25)" stroke="#4cf" stroke-width="1.5"/>
                <circle cx="${cx}" cy="${cy}" r="2" fill="#4cf"/>
                ${labels}
                <text x="${cx}" y="140" text-anchor="middle" font-size="7" fill="#888">Scale: 100 max | gold = over</text>
            </svg>`;
        }

        // ── Main tooltip container ────────────────────────────────────────
        const tooltip = document.createElement('div');
        tooltip.id = 'adpt-graph-tooltip';
        tooltip.style.cssText = `
            position: absolute;
            background: rgba(15, 15, 15, 0.97);
            border: 1px solid #555;
            border-radius: 6px;
            padding: 6px 8px;
            z-index: 999999;
            box-shadow: 0 4px 14px rgba(0,0,0,0.9);
            width: 170px;
            pointer-events: auto;
            color: white;
            font-family: Arial, sans-serif;
            font-size: 9px;
        `;

        // ── 1. Damage-per-turn bar chart (existing) ───────────────────────
        const chartTitle = document.createElement('div');
        chartTitle.style.cssText = 'font-size:8px; color:#aaa; margin-bottom:3px; text-align:center;';
        chartTitle.textContent = 'Damage Per Turn';
        tooltip.appendChild(chartTitle);

        const mainArea = document.createElement('div');
        mainArea.style.cssText = 'display: flex; height: 40px; width: 100%; gap: 4px; margin-bottom: 6px;';

        const yAxis = document.createElement('div');
        yAxis.style.cssText = 'display: flex; flex-direction: column; justify-content: space-between; align-items: flex-end; font-size: 8px; color: #aaa; min-width: 22px; padding-bottom: 1px;';
        const maxLabel = document.createElement('span');
        const minLabel = document.createElement('span');
        yAxis.appendChild(maxLabel);
        yAxis.appendChild(minLabel);

        const graphContainer = document.createElement('div');
        graphContainer.style.cssText = 'display: flex; align-items: flex-end; flex: 1; height: 100%; gap: 1px; border-bottom: 1px solid #777; border-left: 1px solid #777;';

        const renderBarChart = (histData) => {
            const hi = Math.max(...histData, 1);
            const lo = Math.min(...histData);
            maxLabel.innerText = hi.toFixed(1);
            minLabel.innerText = lo.toFixed(1);
            graphContainer.innerHTML = '';
            histData.forEach((dmg, idx) => {
                const bar = document.createElement('div');
                const h = hi > 0 ? (dmg / hi * 100) : 0;
                bar.style.cssText = `flex: 1; background: ${getADPTColor(dmg)}; height: ${h}%; min-height: 1px; transition: height 0.15s, background 0.15s; cursor: crosshair;`;
                bar.title = `Turn ${idx + 1}: ${dmg.toFixed(1)} dmg`;
                bar.onmouseover = () => bar.style.opacity = '0.6';
                bar.onmouseout = () => bar.style.opacity = '1';
                graphContainer.appendChild(bar);
            });
        };
        renderBarChart(history);

        mainArea.appendChild(yAxis);
        mainArea.appendChild(graphContainer);
        tooltip.appendChild(mainArea);

        // ── 2 & 3. Pentagon + Armor selector (single shared scope) ────────
        if (weaponContext) {
            // ── separator ──────────────────────────────────────────────────
            const sep1 = document.createElement('div');
            sep1.style.cssText = 'border-top: 1px solid #333; margin: 4px 0;';
            tooltip.appendChild(sep1);

            // ── Pentagon title ──────────────────────────────────────────────
            const penTitle = document.createElement('div');
            penTitle.style.cssText = 'font-size:8px; color:#7af; margin-bottom:2px; text-align:center; font-weight:bold;';
            penTitle.textContent = '⬠ Build Impact Radar';
            tooltip.appendChild(penTitle);

            // ── Pentagon container ──────────────────────────────────────────
            const penDiv = document.createElement('div');
            penDiv.style.cssText = 'display:flex; justify-content:center; margin: 0 auto;';
            tooltip.appendChild(penDiv);

            // renderPentagon declared here — shared scope with armor radio handler
            // Fixed outer scale = 100; scores >100 extend beyond the outer ring
            const renderPentagon = () => {
                const ps = pentagonBuilds.map(b => ({ ...b, score: getBuildADPT(b.id) }));
                penDiv.innerHTML = buildPentagonSVG(ps, 100);
            };
            renderPentagon();

            // ── separator ──────────────────────────────────────────────────
            const sep2 = document.createElement('div');
            sep2.style.cssText = 'border-top: 1px solid #333; margin: 6px 0 3px 0;';
            tooltip.appendChild(sep2);

            // ── Armor title ─────────────────────────────────────────────────
            const armorTitle = document.createElement('div');
            armorTitle.style.cssText = 'font-size:8px; color:#fca; margin-bottom:3px; text-align:center; font-weight:bold;';
            armorTitle.textContent = '🛡 Target Armor';
            tooltip.appendChild(armorTitle);

            // ── ADPT-vs-armor display ───────────────────────────────────────
            const adptDisplay = document.createElement('div');
            adptDisplay.style.cssText = 'text-align:center; font-size:9px; margin-top:3px; padding:2px 4px; background:rgba(255,255,255,0.07); border-radius:3px;';

            const updateArmorDisplay = (armorId) => {
                const result = getArmorADPT(armorId);
                const opt = ARMOR_OPTIONS.find(o => o.id === armorId);
                const color = getADPTColor(parseFloat(result.score));
                adptDisplay.innerHTML = `ADPT vs <b style="color:#fca">${opt.label.split(' ')[0]}</b>: <span style="color:${color};font-weight:bold;">${result.score}</span>`;
            };

            // ── Radio rows ──────────────────────────────────────────────────
            const armorContainer = document.createElement('div');
            armorContainer.style.cssText = 'display:flex; flex-direction:column; gap:3px;';

            ARMOR_OPTIONS.forEach(opt => {
                const row = document.createElement('label');
                row.style.cssText = 'display:flex; align-items:center; gap:5px; cursor:pointer; padding:1px 3px; border-radius:3px; transition:background 0.15s;';
                row.onmouseover = () => row.style.background = 'rgba(255,255,255,0.06)';
                row.onmouseout = () => row.style.background = '';

                const radio = document.createElement('input');
                radio.type = 'radio';
                radio.name = 'adpt-armor-target';
                radio.value = opt.id;
                radio.checked = (opt.id === selectedArmorId);
                radio.style.cssText = 'cursor:pointer; accent-color:#4cf; margin:0; flex-shrink:0;';

                const lbl = document.createElement('span');
                lbl.style.cssText = 'font-size:8px; color:#ddd; flex:1;';
                lbl.textContent = opt.label;

                radio.addEventListener('change', () => {
                    selectedArmorId = opt.id;
                    adptStorage.set('target_armor', opt.id);
                    // Update armor ADPT text display
                    updateArmorDisplay(opt.id);
                    // Re-render pentagon with new armor
                    renderPentagon();
                    // Re-render bar chart with fresh history for new armor
                    const newResult = getArmorADPT(opt.id);
                    // DEBUG: log armor switch
                    dbg(
                        '%c[ADPT Debug]%c Armor changed → ' + opt.id,
                        'color:#fca;font-weight:bold', 'color:#fff',
                        '\n  Weapon      :', weaponContext ? weaponContext.name : 'n/a',
                        '\n  isMelee     :', isMelee,
                        '\n  armorMelee% :', opt.armorMelee, '| armorGun%:', opt.armorGun,
                        '\n  Applied arm%:', isMelee ? opt.armorMelee + ' (melee path)' : opt.armorGun + ' (gun path)',
                        '\n  New ADPT    :', newResult.score
                    );
                    renderBarChart(newResult.history);
                    // Force re-injection of all page ADPT badges with new armor
                    document.querySelectorAll('[data-v30="true"]').forEach(el => {
                        el.removeAttribute('data-v30');
                        el.removeAttribute('data-last-ammo');
                    });
                    mainInject();
                });

                row.appendChild(radio);
                row.appendChild(lbl);
                armorContainer.appendChild(row);
            });

            tooltip.appendChild(armorContainer);
            tooltip.appendChild(adptDisplay);
            updateArmorDisplay(selectedArmorId);
        }

                // ── Dismiss logic ─────────────────────────────────────────────────
        // No stopPropagation on tooltip — would block radio label→input clicks
        document.body.appendChild(tooltip);

        const rect = e.target.getBoundingClientRect();
        let left = rect.left + window.scrollX - 85 + (rect.width / 2);
        let top = rect.top + window.scrollY - 60;
        // Clamp to viewport
        left = Math.max(5, Math.min(left, window.innerWidth + window.scrollX - 185));
        top = Math.max(5, top);
        tooltip.style.left = `${left}px`;
        tooltip.style.top = `${top}px`;

        const removeTooltip = (evt) => {
            // Only remove if click is outside the tooltip
            if (tooltip && tooltip.parentNode && !tooltip.contains(evt.target)) {
                tooltip.remove();
                document.removeEventListener('click', removeTooltip);
            }
        };
        setTimeout(() => document.addEventListener('click', removeTooltip), 10);
    }


    function makeShowGraphHandler(historyToUse, n, d, a, activeBonuses, ammoType) {
        const wData = WEAPON_DB[n];
        // isMelee: weapon is in WEAPON_DB AND has mag===0 (swords/knives/blunt)
        // Unknown weapons default to false (gun) — never mag:0 fallback for unknown
        const isMeleeCtx = wData != null ? wData.mag === 0 : false;
        const ctx = { name: n, baseDmg: d, baseAcc: a, activeBonuses: activeBonuses, ammoType: ammoType || '', isMelee: isMeleeCtx };
        return function(e) { showGraphTooltip(e, historyToUse, ctx); };
    }

    function createADPTElement(displayText, adptValueForColor, activeBonuses, historyToUse, weaponCtx) {
        const color = getADPTColor(parseFloat(adptValueForColor));
        const div = document.createElement('div');
        div.className = 'adpt-badge';
        div.style.cssText = `color: ${color}; font-weight: bold; font-size: 12px; margin-top: 4px; border-top: 1px solid #333; padding: 2px 0; text-shadow: 1px 1px 0 #000; cursor: pointer;`;
        div.innerHTML = displayText;
        if(activeBonuses.length > 0) div.title = activeBonuses.map(b => b.name).join(' & ') + " (Click for Graph)";
        div.addEventListener('click', (e) => showGraphTooltip(e, historyToUse, weaponCtx || null));
        return div;
    }

   function injectItemMarket() {
        // Tìm thẻ bao ngoài của item bằng chuỗi linh hoạt
        document.querySelectorAll('[class*="itemTile_"]:not([data-v30])').forEach(item => {
            try {
                // Thêm chữ 'i' để không phân biệt hoa thường (Damage hay damage đều nhận)
                const dmgEl = item.querySelector('[aria-label*="damage" i] span[aria-hidden="true"]');
                const accEl = item.querySelector('[aria-label*="accuracy" i] span[aria-hidden="true"]');
                if(!dmgEl || !accEl) return;

                const d = parseFloat(dmgEl.innerText);
                const a = parseFloat(accEl.innerText);

                // Tìm thẻ Tên vũ khí (vd: Thompson)
                const nameEl = item.querySelector('[class*="name_"]');
                if(!nameEl) return;
                const n = nameEl.innerText.trim();

                let activeBonuses = [];
                // Tìm các hiệu ứng vũ khí
                item.querySelectorAll('[class*="bonuses_"] i, [data-bonus-name]').forEach(node => {
                    let bN = node.getAttribute('data-bonus-attachment-title') || node.getAttribute('data-bonus-name') || "";
                    let bV = 0;
                    const m = (node.getAttribute('aria-label') || "").match(/(\d+)/);
                    if (m) bV = parseInt(m[1]);
                    if (bN && BONUS_DB[bN]) activeBonuses.push({ name: bN, val: bV });
                });

                const adptStandard = simulateADPT(n, d, a, activeBonuses);
                let displayText = `[ ${adptStandard.score} ]`, valueForColor = adptStandard.score;
                let historyToUse = adptStandard.history;

                if (JAPANESE_WEAPONS.includes(n)) {
                    const adptJap = simulateADPT(n, d, a, [...activeBonuses, {name: "Japanese Blade Mastery", val: 10}]);
                    displayText = `[${adptStandard.score} / ${adptJap.score}]`;
                    valueForColor = adptJap.score;
                    historyToUse = adptJap.history;
                }

                // [FIX QUAN TRỌNG]: Đặt Element ADPT vào đúng khu vực chứa tên vũ khí thay vì nút View Info
                const titleContainer = nameEl.parentNode;
                if(titleContainer) {
                    titleContainer.querySelectorAll('.adpt-badge').forEach(el => el.remove());
                    const _wM = WEAPON_DB[n]; const wCtxM = { name: n, baseDmg: d, baseAcc: a, activeBonuses: activeBonuses, ammoType: '', isMelee: _wM != null ? _wM.mag === 0 : false };
                    titleContainer.appendChild(createADPTElement(displayText, valueForColor, activeBonuses, historyToUse, wCtxM));
                }

                item.setAttribute('data-v30', 'true');
            } catch (e) { console.error("ADPT Item Market Error:", e); }
        });
    }

    function injectAuctionHouse() {
        document.querySelectorAll('li .item-cont-wrap:not([data-v30])').forEach(wrap => {
            try {
                const nameEl = wrap.querySelector('.item-name'); if (!nameEl) return;
                let n = nameEl.innerText.split('[')[0].trim();
                const d = parseFloat(wrap.querySelector('.bonus-attachment-item-damage-bonus').parentElement.querySelector('.label-value').innerText);
                const a = parseFloat(wrap.querySelector('.bonus-attachment-item-accuracy-bonus').parentElement.querySelector('.label-value').innerText);
                let activeBonuses = [];
                wrap.querySelectorAll('.bonus-attachment-icons').forEach(node => {
                    let titleText = document.createElement("textarea"); titleText.innerHTML = node.getAttribute('title') || "";
                    for (const key in BONUS_DB) {
                        if (titleText.value.includes(key)) {
                            const m = titleText.value.match(/(\d+)%/);
                            if (m) activeBonuses.push({ name: key, val: parseInt(m[1]) });
                            break;
                        }
                    }
                });

                const adptStandard = simulateADPT(n, d, a, activeBonuses);
                let displayText = `[ ${adptStandard.score} ]`, valueForColor = adptStandard.score;
                let historyToUse = adptStandard.history;

                if (JAPANESE_WEAPONS.includes(n)) {
                    const adptJap = simulateADPT(n, d, a, [...activeBonuses, {name: "Japanese Blade Mastery", val: 10}]);
                    displayText = `[${adptStandard.score} / ${adptJap.score}]`;
                    valueForColor = adptJap.score;
                    historyToUse = adptJap.history;
                }
                const titleCont = wrap.querySelector('.title');
                if (titleCont) {
                    titleCont.querySelectorAll('.adpt-badge').forEach(el => el.remove());
                    const _wA = WEAPON_DB[n]; const wCtxA = { name: n, baseDmg: d, baseAcc: a, activeBonuses: activeBonuses, ammoType: '', isMelee: _wA != null ? _wA.mag === 0 : false };
                    titleCont.appendChild(createADPTElement(displayText, valueForColor, activeBonuses, historyToUse, wCtxA));
                }
                wrap.setAttribute('data-v30', 'true');
            } catch (e) { }
        });
    }

    function injectInventory() {
        document.querySelectorAll('li[data-weaponinfo="1"]:not([data-v30])').forEach(item => {
            try {
                const nameEl = item.querySelector('.name-wrap .name');
                if (!nameEl) return;
                const n = nameEl.innerText.trim();

                const dmgIconEl = item.querySelector('.bonus-attachment-item-damage-bonus');
                const accIconEl = item.querySelector('.bonus-attachment-item-accuracy-bonus');
                if (!dmgIconEl || !accIconEl) return;

                const d = parseFloat(dmgIconEl.nextElementSibling.innerText);
                const a = parseFloat(accIconEl.nextElementSibling.innerText);

                let activeBonuses = [];
                item.querySelectorAll('.bonuses-wrap i[title]').forEach(node => {
                    let titleText = document.createElement("textarea");
                    titleText.innerHTML = node.getAttribute('title') || "";
                    for (const key in BONUS_DB) {
                        if (titleText.value.includes(key)) {
                            const m = titleText.value.match(/(\d+)%/);
                            if (m) activeBonuses.push({ name: key, val: parseInt(m[1]) });
                            break;
                        }
                    }
                });

                const adptStandard = simulateADPT(n, d, a, activeBonuses);
                let displayText = `[ ${adptStandard.score} ]`, valueForColor = adptStandard.score;
                let historyToUse = adptStandard.history;

                if (JAPANESE_WEAPONS.includes(n)) {
                    const adptJap = simulateADPT(n, d, a, [...activeBonuses, {name: "Japanese Blade Mastery", val: 10}]);
                    displayText = `[${adptStandard.score}/${adptJap.score}]`;
                    valueForColor = adptJap.score;
                    historyToUse = adptJap.history;
                }

                nameEl.parentNode.querySelectorAll('.adpt-badge').forEach(el => el.remove());
                const adptSpan = document.createElement('span');
                adptSpan.className = 'adpt-badge';
                const color = getADPTColor(parseFloat(valueForColor));
                adptSpan.style.cssText = `color: ${color}; margin-right: 6px; font-weight: bold; font-size: 12px; text-shadow: 1px 1px 0 #000; background-color: rgba(0,0,0,0.3); padding: 1px 4px; border-radius: 3px; cursor: pointer;`;
                adptSpan.innerHTML = displayText;
                if(activeBonuses.length > 0) adptSpan.title = activeBonuses.map(b => b.name).join(' & ') + " (Click for Graph)";
                adptSpan.addEventListener('click', makeShowGraphHandler(historyToUse, n, d, a, activeBonuses, ''));
                nameEl.parentNode.insertBefore(adptSpan, nameEl);
                item.setAttribute('data-v30', 'true');
            } catch (e) { }
        });
    }

    function injectFactionArmory() {
        document.querySelectorAll('li:not([data-v30])').forEach(item => {
            try {
                // Determine if it's an armory item via data-armoryid
                const armoryWrap = item.querySelector('.img-wrap[data-armoryid]');
                if (!armoryWrap) return;

                const nameEl = item.querySelector('.name');
                if (!nameEl) return;
                const n = nameEl.innerText.trim();

                const dmgIconEl = item.querySelector('.bonus-attachment-item-damage-bonus');
                const accIconEl = item.querySelector('.bonus-attachment-item-accuracy-bonus');
                // Only process items that have weapons stats
                if (!dmgIconEl || !accIconEl) return;

                const d = parseFloat(dmgIconEl.nextElementSibling.innerText);
                const a = parseFloat(accIconEl.nextElementSibling.innerText);

                let activeBonuses = [];
                // Retrieve bonuses inside ul.bonuses
                item.querySelectorAll('.bonuses .bonus i').forEach(node => {
                    let titleText = document.createElement("textarea");
                    titleText.innerHTML = node.getAttribute('title') || node.getAttribute('aria-label') || node.getAttribute('data-bonus-name') || node.className || "";

                    for (const key in BONUS_DB) {
                        if (titleText.value.includes(key) || titleText.value.replace(/-/g, " ").includes(key.toLowerCase())) {
                            const m = titleText.value.match(/(\d+)%/);
                            if (m) {
                                activeBonuses.push({ name: key, val: parseInt(m[1]) });
                            } else {
                                const classMatch = node.className.match(/(\d+)/);
                                if (classMatch) {
                                    activeBonuses.push({ name: key, val: parseInt(classMatch[1]) });
                                }
                            }
                            break;
                        }
                    }
                });

                const adptStandard = simulateADPT(n, d, a, activeBonuses);
                let displayText = `[ ${adptStandard.score} ]`, valueForColor = adptStandard.score;
                let historyToUse = adptStandard.history;

                if (JAPANESE_WEAPONS.includes(n)) {
                    const adptJap = simulateADPT(n, d, a, [...activeBonuses, {name: "Japanese Blade Mastery", val: 10}]);
                    displayText = `[${adptStandard.score} / ${adptJap.score}]`;
                    valueForColor = adptJap.score;
                    historyToUse = adptJap.history;
                }

                // Remove old badge, then inject fresh one BEFORE the item name text
                nameEl.querySelectorAll('.adpt-badge').forEach(el => el.remove());
                const adptSpan = document.createElement('span');
                adptSpan.className = 'adpt-badge';
                const color = getADPTColor(parseFloat(valueForColor));
                adptSpan.style.cssText = `color: ${color}; margin-right: 8px; font-weight: bold; font-size: 12px; text-shadow: 1px 1px 0 #000; cursor: pointer; display: inline-block;`;
                adptSpan.innerHTML = displayText;
                if(activeBonuses.length > 0) adptSpan.title = activeBonuses.map(b => b.name).join(' & ') + " (Click for Graph)";
                adptSpan.addEventListener('click', makeShowGraphHandler(historyToUse, n, d, a, activeBonuses, ''));
                nameEl.insertBefore(adptSpan, nameEl.firstChild);
                item.setAttribute('data-v30', 'true');
            } catch (e) { console.error("ADPT Faction Scan Error:", e); }
        });
    }

    function injectItemReview() {
        // Đổi .itemReview___b8uF1 thành [class*="itemReview_"]
        document.querySelectorAll('[class*="itemReview_"]').forEach(review => {
            try {
                let ammoType = "";
                // Đổi .ammo___fMLmp thành [class*="ammo_"]
                const ammoEl = review.querySelector('[class*="ammo_"]');
                if (ammoEl) {
                    const ammoLabel = ammoEl.getAttribute('aria-label') || "";
                    const ammoMatch = ammoLabel.match(/Ammo type is "([^"]+)"/);
                    if (ammoMatch) {
                        ammoType = ammoMatch[1];
                    }
                }

                const lastAmmo = review.getAttribute('data-last-ammo');
                if (review.hasAttribute('data-v30') && lastAmmo === ammoType) {
                    return;
                }

                // Đổi .img___lW9P3 thành img[class*="img_"]
                const nameEl = review.querySelector('img[class*="img_"]');
                if (!nameEl) return;
                const n = nameEl.getAttribute('alt').trim();

                // Đổi .damage___NJy0A và .accuracy___udZQR
                const dmgEl = review.querySelector('[class*="damage_"]');
                const accEl = review.querySelector('[class*="accuracy_"]');
                if (!dmgEl || !accEl) return;

                const d = parseFloat(dmgEl.innerText);
                const a = parseFloat(accEl.innerText);

                let activeBonuses = [];
                review.querySelectorAll('button[aria-label*="bonus:"]').forEach(btn => {
                    const label = btn.getAttribute('aria-label') || "";
                    const match = label.match(/"([^"]+)" bonus:.*?(\d+)%/);
                    if (match) {
                        const bN = match[1];
                        const bV = parseInt(match[2]);
                        if (BONUS_DB[bN]) {
                            activeBonuses.push({ name: bN, val: bV });
                        }
                    }
                });

                const adptStandard = simulateADPT(n, d, a, activeBonuses, ammoType);
                let displayText = `[ ${adptStandard.score} ]`;
                let valueForColor = adptStandard.score;
                let historyToUse = adptStandard.history;

                if (JAPANESE_WEAPONS.includes(n)) {
                    const adptJap = simulateADPT(n, d, a, [...activeBonuses, {name: "Japanese Blade Mastery", val: 10}], ammoType);
                    displayText = `[${adptStandard.score}/${adptJap.score}]`;
                    valueForColor = adptJap.score;
                    historyToUse = adptJap.history;
                }

                // Đổi .type___hrzuz thành [class*="type_"]
                const typeEl = review.querySelector('[class*="type_"]');
                if (typeEl) {
                    if (!typeEl.hasAttribute('data-orig-text')) {
                        typeEl.setAttribute('data-orig-text', typeEl.innerText.trim());
                    }

                    const origText = typeEl.getAttribute('data-orig-text');
                    const color = getADPTColor(parseFloat(valueForColor));
                    let titleTooltip = activeBonuses.map(b => b.name).join(' & ');
                    if (ammoType) titleTooltip += (titleTooltip ? ' + ' : '') + ammoType + ' Ammo';
                    titleTooltip += " (Click for Graph)";

                    typeEl.innerHTML = ``;
                    const adptSpan = document.createElement('span');
                    adptSpan.className = 'adpt-badge';
                    adptSpan.style.cssText = `color: ${color}; font-weight: bold; text-shadow: 1px 1px 0 #000; cursor: pointer;`;
                    adptSpan.title = titleTooltip;
                    adptSpan.innerHTML = displayText;
                    adptSpan.addEventListener('click', makeShowGraphHandler(historyToUse, n, d, a, activeBonuses, ammoType));
                    typeEl.appendChild(adptSpan);
                }

                review.setAttribute('data-v30', 'true');
                review.setAttribute('data-last-ammo', ammoType);
            } catch (e) { console.error("ADPT Item Review Error:", e); }
        });
    }

    function mainInject() {
        if (location.href.includes('amarket.php')) injectAuctionHouse();
        else if (location.href.includes('item.php')) injectInventory();
        else if (location.href.includes('factions.php')) injectFactionArmory();
        else injectItemMarket();

        injectItemReview();
    }

    const obs = new MutationObserver(mainInject);
    obs.observe(document.body, { childList: true, subtree: true });
    mainInject();
})();