ADPT Sim Inventory Support

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

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==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();
})();