From b69eabd1eb4816274c4a6081e58f70d3c11ac9bf Mon Sep 17 00:00:00 2001 From: GaboGG Date: Wed, 22 Jul 2026 06:13:54 -0400 Subject: [PATCH] Fix: Spark of Life icon key, mana items after buffs 1. Spark of Life icon is 'fallenshield.png' not 'spark_of_life' - fixed BUFF_PRIORITY key so buff detection works correctly 2. Mana/spirit items moved to AFTER buffs in all 3 tiers - was drinking mana potions on turn 2 before buffs were cast 3. Priority order: Health items -> Cure -> Buffs -> Mana items -> Skills -> Spirit Stance -> Damage spells -> Channeling -> Attack --- scripts/hv-unified.user.js | 74 +++--- scripts/latest.user.js | 74 +++--- simulator/results/benchmark.json | 414 +++++++++++++++---------------- src/strategy-engine.js | 74 +++--- 4 files changed, 321 insertions(+), 315 deletions(-) diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index 500caa2..a81ecef 100644 --- a/scripts/hv-unified.user.js +++ b/scripts/hv-unified.user.js @@ -679,10 +679,9 @@ function shouldBuff(name, minTurns) { const BUFF_PRIORITY = [ { icon: 'haste', spell: 'Haste', minTurns: 2 }, { icon: 'protection', spell: 'Protection', minTurns: 2 }, - { icon: 'spark_of_life', spell: 'Spark of Life', minTurns: 1 }, + { icon: 'fallenshield', spell: 'Spark of Life', minTurns: 1 }, // icon: fallenshield.png { icon: 'regen', spell: 'Regen', minTurns: 2 }, { icon: 'absorb', spell: 'Absorb', minTurns: 2 }, - // Shadow Veil: icon key = lowercased spell name without spaces { icon: 'shadowveil', spell: 'Shadow Veil', minTurns: 3 }, ]; @@ -844,23 +843,13 @@ function strategyNovice() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard + // 1. Health items — safe to use early if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - // Mana gem — use when MP < 50% - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // Spirit gem — use when SP is low (keeps Spirit Stance available) - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // 2. Cure only if items aren't available and HP is critical + // 2. Cure if (STATE.hp < CFG.cureHP) { const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; @@ -869,12 +858,22 @@ function strategyNovice() { // 3. Buffs — cast if missing or about to expire for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { - // Regen is hp-dependent: only cast if HP is below threshold if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } } + // 4. Mana/spirit items — AFTER buffs, so we don't waste a potion + // before casting spells + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 5. Spirit Stance at OC 60 — but only if we have SP to sustain it if (STATE.oc >= 60 && !STATE.spiritStance && STATE.sp > 0.10) return { type: 'toggle_spirit' }; @@ -954,20 +953,11 @@ function strategyAdept() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard + // 1. Health items — safe to use early if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // Spirit gem — use when SP is low - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } // 2. Cure if (STATE.hp < CFG.cureHP) { @@ -975,7 +965,7 @@ function strategyAdept() { if (c) return { type: 'spell', name: c, selfTarget: true }; } - // 3. Buffs — prioritize by remaining duration (lowest = most urgent) + // 3. Buffs for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; @@ -983,6 +973,16 @@ function strategyAdept() { } } + // 4. Mana/spirit items — AFTER buffs + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 4. Debuffs — only on important fights (rare/boss/ultimate monsters) // In random mob fights, just attack through them if (anyRareMonster() || STATE.monsters.length <= 2) { @@ -1083,19 +1083,11 @@ function strategyVeteran() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard, use at reasonable thresholds + // 1. Health items — safe to use early if (STATE.hp < 0.60) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } // 2. Cure if (STATE.hp < CFG.cureHP) { @@ -1103,7 +1095,7 @@ function strategyVeteran() { if (c) return { type: 'spell', name: c, selfTarget: true }; } - // 3. Buffs — prioritize by remaining duration + // 3. Buffs if (CFG.autoBuff) { for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { @@ -1113,6 +1105,16 @@ function strategyVeteran() { } } + // 4. Mana/spirit items — AFTER buffs + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 4. Debuffs — only on important fights (rare/boss) if (CFG.autoDebuff && STATE.mp > 0.2 && (anyRareMonster() || STATE.monsters.length <= 2)) { if (hasSpell('Imperil')) { diff --git a/scripts/latest.user.js b/scripts/latest.user.js index 500caa2..a81ecef 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -679,10 +679,9 @@ function shouldBuff(name, minTurns) { const BUFF_PRIORITY = [ { icon: 'haste', spell: 'Haste', minTurns: 2 }, { icon: 'protection', spell: 'Protection', minTurns: 2 }, - { icon: 'spark_of_life', spell: 'Spark of Life', minTurns: 1 }, + { icon: 'fallenshield', spell: 'Spark of Life', minTurns: 1 }, // icon: fallenshield.png { icon: 'regen', spell: 'Regen', minTurns: 2 }, { icon: 'absorb', spell: 'Absorb', minTurns: 2 }, - // Shadow Veil: icon key = lowercased spell name without spaces { icon: 'shadowveil', spell: 'Shadow Veil', minTurns: 3 }, ]; @@ -844,23 +843,13 @@ function strategyNovice() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard + // 1. Health items — safe to use early if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - // Mana gem — use when MP < 50% - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // Spirit gem — use when SP is low (keeps Spirit Stance available) - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // 2. Cure only if items aren't available and HP is critical + // 2. Cure if (STATE.hp < CFG.cureHP) { const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; @@ -869,12 +858,22 @@ function strategyNovice() { // 3. Buffs — cast if missing or about to expire for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { - // Regen is hp-dependent: only cast if HP is below threshold if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } } + // 4. Mana/spirit items — AFTER buffs, so we don't waste a potion + // before casting spells + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 5. Spirit Stance at OC 60 — but only if we have SP to sustain it if (STATE.oc >= 60 && !STATE.spiritStance && STATE.sp > 0.10) return { type: 'toggle_spirit' }; @@ -954,20 +953,11 @@ function strategyAdept() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard + // 1. Health items — safe to use early if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // Spirit gem — use when SP is low - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } // 2. Cure if (STATE.hp < CFG.cureHP) { @@ -975,7 +965,7 @@ function strategyAdept() { if (c) return { type: 'spell', name: c, selfTarget: true }; } - // 3. Buffs — prioritize by remaining duration (lowest = most urgent) + // 3. Buffs for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; @@ -983,6 +973,16 @@ function strategyAdept() { } } + // 4. Mana/spirit items — AFTER buffs + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 4. Debuffs — only on important fights (rare/boss/ultimate monsters) // In random mob fights, just attack through them if (anyRareMonster() || STATE.monsters.length <= 2) { @@ -1083,19 +1083,11 @@ function strategyVeteran() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard, use at reasonable thresholds + // 1. Health items — safe to use early if (STATE.hp < 0.60) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } // 2. Cure if (STATE.hp < CFG.cureHP) { @@ -1103,7 +1095,7 @@ function strategyVeteran() { if (c) return { type: 'spell', name: c, selfTarget: true }; } - // 3. Buffs — prioritize by remaining duration + // 3. Buffs if (CFG.autoBuff) { for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { @@ -1113,6 +1105,16 @@ function strategyVeteran() { } } + // 4. Mana/spirit items — AFTER buffs + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 4. Debuffs — only on important fights (rare/boss) if (CFG.autoDebuff && STATE.mp > 0.2 && (anyRareMonster() || STATE.monsters.length <= 2)) { if (hasSpell('Imperil')) { diff --git a/simulator/results/benchmark.json b/simulator/results/benchmark.json index 2c7beaa..74e653c 100644 --- a/simulator/results/benchmark.json +++ b/simulator/results/benchmark.json @@ -27,241 +27,241 @@ { "name": "chan-mp-focused", "desc": "Channeling + aggressive mana gem use", - "avgRounds": "149.0", - "avgKills": "835.2", + "avgRounds": "151.8", + "avgKills": "846.8", "avgDmgRatio": "2.9", - "avgDmgPerRound": 4304, - "avgExp": 4488560, - "avgCredits": 58783, - "maxRounds": 162, + "avgDmgPerRound": 4310, + "avgExp": 4682000, + "avgCredits": 62540, + "maxRounds": 171, "survivalRate": "0", "deaths": 5, - "score": 83520 - }, - { - "name": "chan-no-buff-refresh", - "desc": "Only cast missing buffs, never refresh early", - "avgRounds": "143.0", - "avgKills": "794.4", - "avgDmgRatio": "3.1", - "avgDmgPerRound": 4252, - "avgExp": 4141360, - "avgCredits": 54902, - "maxRounds": 157, - "survivalRate": "0", - "deaths": 5, - "score": 79440 - }, - { - "name": "chan-aggressive-buff", - "desc": "Channeling via buffs at 20+ turns, keep all buffs fresh", - "avgRounds": "142.4", - "avgKills": "790.2", - "avgDmgRatio": "3.2", - "avgDmgPerRound": 4440, - "avgExp": 4093520, - "avgCredits": 51455, - "maxRounds": 149, - "survivalRate": "0", - "deaths": 5, - "score": 79020 - }, - { - "name": "chan-buff-only", - "desc": "Channeling via buff refresh only, no attack spells", - "avgRounds": "140.6", - "avgKills": "777.0", - "avgDmgRatio": "3.3", - "avgDmgPerRound": 4500, - "avgExp": 4052960, - "avgCredits": 54172, - "maxRounds": 166, - "survivalRate": "0", - "deaths": 5, - "score": 77700 - }, - { - "name": "conservative-items", - "desc": "Save items, use Cure more", - "avgRounds": "139.8", - "avgKills": "773.0", - "avgDmgRatio": "3.3", - "avgDmgPerRound": 4480, - "avgExp": 3997360, - "avgCredits": 53493, - "maxRounds": 163, - "survivalRate": "0", - "deaths": 5, - "score": 77300 - }, - { - "name": "full-conservative", - "desc": "Late spirit, save items, no attack spells", - "avgRounds": "136.8", - "avgKills": "759.0", - "avgDmgRatio": "3.3", - "avgDmgPerRound": 4413, - "avgExp": 3777680, - "avgCredits": 47629, - "maxRounds": 148, - "survivalRate": "0", - "deaths": 5, - "score": 75900 - }, - { - "name": "chan-cheap-spell", - "desc": "Channeling via cheapest attack spell (4 MP)", - "avgRounds": "137.4", - "avgKills": "757.8", - "avgDmgRatio": "3.1", - "avgDmgPerRound": 4250, - "avgExp": 3854800, - "avgCredits": 52097, - "maxRounds": 162, - "survivalRate": "0", - "deaths": 5, - "score": 75780 - }, - { - "name": "chan-spirit-60", - "desc": "Channeling + spirit stance at 60", - "avgRounds": "136.8", - "avgKills": "755.4", - "avgDmgRatio": "3.3", - "avgDmgPerRound": 4216, - "avgExp": 3785840, - "avgCredits": 51913, - "maxRounds": 153, - "survivalRate": "0", - "deaths": 5, - "score": 75540 + "score": 84680 }, { "name": "chan-always-regen", "desc": "Channeling: always cast Regen first if not active", - "avgRounds": "134.2", - "avgKills": "739.8", - "avgDmgRatio": "3.4", - "avgDmgPerRound": 4441, - "avgExp": 3694400, - "avgCredits": 51225, - "maxRounds": 158, - "survivalRate": "0", - "deaths": 5, - "score": 73980 - }, - { - "name": "baseline", - "desc": "Current v0.11.0 defaults", - "avgRounds": "130.0", - "avgKills": "712.8", - "avgDmgRatio": "3.5", - "avgDmgPerRound": 4371, - "avgExp": 3440240, - "avgCredits": 46973, + "avgRounds": "143.2", + "avgKills": "796.6", + "avgDmgRatio": "3.3", + "avgDmgPerRound": 4478, + "avgExp": 4137360, + "avgCredits": 53088, "maxRounds": 152, "survivalRate": "0", "deaths": 5, - "score": 71280 + "score": 79660 }, { - "name": "spirit-80", - "desc": "Spirit Stance at OC 80 (late, skills first)", - "avgRounds": "127.8", - "avgKills": "698.6", - "avgDmgRatio": "3.8", - "avgDmgPerRound": 4351, - "avgExp": 3331760, - "avgCredits": 44061, - "maxRounds": 149, + "name": "conservative-items", + "desc": "Save items, use Cure more", + "avgRounds": "141.8", + "avgKills": "787.6", + "avgDmgRatio": "3.2", + "avgDmgPerRound": 4457, + "avgExp": 4090640, + "avgCredits": 55015, + "maxRounds": 162, "survivalRate": "0", "deaths": 5, - "score": 69860 + "score": 78760 + }, + { + "name": "chan-buff-only", + "desc": "Channeling via buff refresh only, no attack spells", + "avgRounds": "141.6", + "avgKills": "786.4", + "avgDmgRatio": "3.3", + "avgDmgPerRound": 4471, + "avgExp": 4058400, + "avgCredits": 55880, + "maxRounds": 154, + "survivalRate": "0", + "deaths": 5, + "score": 78640 + }, + { + "name": "chan-spirit-60", + "desc": "Channeling + spirit stance at 60", + "avgRounds": "141.0", + "avgKills": "783.8", + "avgDmgRatio": "3.1", + "avgDmgPerRound": 4147, + "avgExp": 4022400, + "avgCredits": 53634, + "maxRounds": 159, + "survivalRate": "0", + "deaths": 5, + "score": 78380 + }, + { + "name": "chan-aggressive-buff", + "desc": "Channeling via buffs at 20+ turns, keep all buffs fresh", + "avgRounds": "140.8", + "avgKills": "781.0", + "avgDmgRatio": "3.4", + "avgDmgPerRound": 4446, + "avgExp": 4092800, + "avgCredits": 55232, + "maxRounds": 173, + "survivalRate": "0", + "deaths": 5, + "score": 78100 + }, + { + "name": "chan-cheap-spell", + "desc": "Channeling via cheapest attack spell (4 MP)", + "avgRounds": "140.6", + "avgKills": "778.2", + "avgDmgRatio": "3.0", + "avgDmgPerRound": 4246, + "avgExp": 4041760, + "avgCredits": 53287, + "maxRounds": 161, + "survivalRate": "0", + "deaths": 5, + "score": 77820 + }, + { + "name": "full-conservative", + "desc": "Late spirit, save items, no attack spells", + "avgRounds": "140.0", + "avgKills": "776.0", + "avgDmgRatio": "3.3", + "avgDmgPerRound": 4379, + "avgExp": 3977920, + "avgCredits": 52946, + "maxRounds": 155, + "survivalRate": "0", + "deaths": 5, + "score": 77600 + }, + { + "name": "chan-no-buff-refresh", + "desc": "Only cast missing buffs, never refresh early", + "avgRounds": "136.4", + "avgKills": "754.0", + "avgDmgRatio": "3.3", + "avgDmgPerRound": 4239, + "avgExp": 3764560, + "avgCredits": 49857, + "maxRounds": 146, + "survivalRate": "0", + "deaths": 5, + "score": 75400 }, { "name": "attack-spells", "desc": "Use cheap attack spells for channeling", + "avgRounds": "130.2", + "avgKills": "718.0", + "avgDmgRatio": "3.4", + "avgDmgPerRound": 4199, + "avgExp": 3463600, + "avgCredits": 44691, + "maxRounds": 150, + "survivalRate": "0", + "deaths": 5, + "score": 71800 + }, + { + "name": "spirit-80", + "desc": "Spirit Stance at OC 80 (late, skills first)", "avgRounds": "126.8", - "avgKills": "692.2", - "avgDmgRatio": "3.5", - "avgDmgPerRound": 4209, - "avgExp": 3274880, - "avgCredits": 42980, - "maxRounds": 152, - "survivalRate": "0", - "deaths": 5, - "score": 69220 - }, - { - "name": "skills-at-60", - "desc": "Skills fire at OC 60 instead of 80", - "avgRounds": "125.6", - "avgKills": "685.0", + "avgKills": "691.8", "avgDmgRatio": "3.6", - "avgDmgPerRound": 4469, - "avgExp": 3200400, - "avgCredits": 41273, - "maxRounds": 137, + "avgDmgPerRound": 4309, + "avgExp": 3283360, + "avgCredits": 42557, + "maxRounds": 147, "survivalRate": "0", "deaths": 5, - "score": 68500 - }, - { - "name": "spirit-40", - "desc": "Spirit Stance at OC 40 (early)", - "avgRounds": "117.8", - "avgKills": "637.2", - "avgDmgRatio": "4.3", - "avgDmgPerRound": 4401, - "avgExp": 2806160, - "avgCredits": 36809, - "maxRounds": 122, - "survivalRate": "0", - "deaths": 5, - "score": 63720 - }, - { - "name": "full-aggressive", - "desc": "Early spirit + aggressive items + attack spells", - "avgRounds": "117.2", - "avgKills": "632.6", - "avgDmgRatio": "4.0", - "avgDmgPerRound": 4226, - "avgExp": 2801680, - "avgCredits": 38420, - "maxRounds": 141, - "survivalRate": "0", - "deaths": 5, - "score": 63260 - }, - { - "name": "aggressive-items", - "desc": "Use items very proactively", - "avgRounds": "116.2", - "avgKills": "629.0", - "avgDmgRatio": "4.0", - "avgDmgPerRound": 4302, - "avgExp": 2730480, - "avgCredits": 36126, - "maxRounds": 125, - "survivalRate": "0", - "deaths": 5, - "score": 62900 + "score": 69180 }, { "name": "balanced", "desc": "Mid spirit, moderate items, attack spells for chan", - "avgRounds": "115.2", - "avgKills": "623.6", - "avgDmgRatio": "4.0", - "avgDmgPerRound": 4097, - "avgExp": 2690320, - "avgCredits": 35489, - "maxRounds": 126, + "avgRounds": "126.4", + "avgKills": "689.2", + "avgDmgRatio": "3.4", + "avgDmgPerRound": 4106, + "avgExp": 3240560, + "avgCredits": 42024, + "maxRounds": 138, "survivalRate": "0", "deaths": 5, - "score": 62360 + "score": 68920 + }, + { + "name": "skills-at-60", + "desc": "Skills fire at OC 60 instead of 80", + "avgRounds": "126.4", + "avgKills": "687.4", + "avgDmgRatio": "3.7", + "avgDmgPerRound": 4300, + "avgExp": 3273680, + "avgCredits": 42465, + "maxRounds": 147, + "survivalRate": "0", + "deaths": 5, + "score": 68740 + }, + { + "name": "spirit-40", + "desc": "Spirit Stance at OC 40 (early)", + "avgRounds": "124.2", + "avgKills": "675.4", + "avgDmgRatio": "3.9", + "avgDmgPerRound": 4354, + "avgExp": 3141920, + "avgCredits": 42525, + "maxRounds": 147, + "survivalRate": "0", + "deaths": 5, + "score": 67540 + }, + { + "name": "baseline", + "desc": "Current v0.11.0 defaults", + "avgRounds": "121.8", + "avgKills": "666.0", + "avgDmgRatio": "3.8", + "avgDmgPerRound": 4336, + "avgExp": 3015760, + "avgCredits": 40701, + "maxRounds": 136, + "survivalRate": "0", + "deaths": 5, + "score": 66600 + }, + { + "name": "aggressive-items", + "desc": "Use items very proactively", + "avgRounds": "117.4", + "avgKills": "631.4", + "avgDmgRatio": "4.1", + "avgDmgPerRound": 4289, + "avgExp": 2803200, + "avgCredits": 38028, + "maxRounds": 136, + "survivalRate": "0", + "deaths": 5, + "score": 63140 + }, + { + "name": "full-aggressive", + "desc": "Early spirit + aggressive items + attack spells", + "avgRounds": "103.6", + "avgKills": "550.6", + "avgDmgRatio": "4.3", + "avgDmgPerRound": 4083, + "avgExp": 2205200, + "avgCredits": 31050, + "maxRounds": 129, + "survivalRate": "0", + "deaths": 5, + "score": 55060 } ], - "timestamp": "2026-07-21T18:12:28.425Z" + "timestamp": "2026-07-21T18:46:13.922Z" } \ No newline at end of file diff --git a/src/strategy-engine.js b/src/strategy-engine.js index b066b98..6320e4b 100644 --- a/src/strategy-engine.js +++ b/src/strategy-engine.js @@ -34,10 +34,9 @@ function shouldBuff(name, minTurns) { const BUFF_PRIORITY = [ { icon: 'haste', spell: 'Haste', minTurns: 2 }, { icon: 'protection', spell: 'Protection', minTurns: 2 }, - { icon: 'spark_of_life', spell: 'Spark of Life', minTurns: 1 }, + { icon: 'fallenshield', spell: 'Spark of Life', minTurns: 1 }, // icon: fallenshield.png { icon: 'regen', spell: 'Regen', minTurns: 2 }, { icon: 'absorb', spell: 'Absorb', minTurns: 2 }, - // Shadow Veil: icon key = lowercased spell name without spaces { icon: 'shadowveil', spell: 'Shadow Veil', minTurns: 3 }, ]; @@ -199,23 +198,13 @@ function strategyNovice() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard + // 1. Health items — safe to use early if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - // Mana gem — use when MP < 50% - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // Spirit gem — use when SP is low (keeps Spirit Stance available) - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // 2. Cure only if items aren't available and HP is critical + // 2. Cure if (STATE.hp < CFG.cureHP) { const c = hasSpell('Full-Cure') ? 'Full-Cure' : hasSpell('Cure') ? 'Cure' : null; if (c) return { type: 'spell', name: c, selfTarget: true }; @@ -224,12 +213,22 @@ function strategyNovice() { // 3. Buffs — cast if missing or about to expire for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { - // Regen is hp-dependent: only cast if HP is below threshold if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; return { type: 'spell', name: b.spell, selfTarget: true }; } } + // 4. Mana/spirit items — AFTER buffs, so we don't waste a potion + // before casting spells + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 5. Spirit Stance at OC 60 — but only if we have SP to sustain it if (STATE.oc >= 60 && !STATE.spiritStance && STATE.sp > 0.10) return { type: 'toggle_spirit' }; @@ -309,20 +308,11 @@ function strategyAdept() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard + // 1. Health items — safe to use early if (STATE.hp < CFG.cureItemHP) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - // Spirit gem — use when SP is low - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } // 2. Cure if (STATE.hp < CFG.cureHP) { @@ -330,7 +320,7 @@ function strategyAdept() { if (c) return { type: 'spell', name: c, selfTarget: true }; } - // 3. Buffs — prioritize by remaining duration (lowest = most urgent) + // 3. Buffs for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { if (b.icon === 'regen' && STATE.hp >= CFG.cureRegenHP) continue; @@ -338,6 +328,16 @@ function strategyAdept() { } } + // 4. Mana/spirit items — AFTER buffs + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 4. Debuffs — only on important fights (rare/boss/ultimate monsters) // In random mob fights, just attack through them if (anyRareMonster() || STATE.monsters.length <= 2) { @@ -438,19 +438,11 @@ function strategyVeteran() { if (gem && !STATE.channeling) return { type: 'item', id: gem.id, selfTarget: true }; - // 1. Gems proactively — don't hoard, use at reasonable thresholds + // 1. Health items — safe to use early if (STATE.hp < 0.60) { const gem = hasUsableGem('heal'); if (gem) return { type: 'item', id: gem.id, selfTarget: true }; } - if (STATE.mp < CFG.manaGemMP) { - const gem = hasUsableGem('mana'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } - if (STATE.sp < CFG.spiritPotionSP) { - const gem = hasUsableGem('spirit'); - if (gem) return { type: 'item', id: gem.id, selfTarget: true }; - } // 2. Cure if (STATE.hp < CFG.cureHP) { @@ -458,7 +450,7 @@ function strategyVeteran() { if (c) return { type: 'spell', name: c, selfTarget: true }; } - // 3. Buffs — prioritize by remaining duration + // 3. Buffs if (CFG.autoBuff) { for (const b of BUFF_PRIORITY) { if (shouldBuff(b.icon, b.minTurns) && STATE.mp > 0.2 && hasSpell(b.spell)) { @@ -468,6 +460,16 @@ function strategyVeteran() { } } + // 4. Mana/spirit items — AFTER buffs + if (STATE.mp < CFG.manaGemMP) { + const gem = hasUsableGem('mana'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + if (STATE.sp < CFG.spiritPotionSP) { + const gem = hasUsableGem('spirit'); + if (gem) return { type: 'item', id: gem.id, selfTarget: true }; + } + // 4. Debuffs — only on important fights (rare/boss) if (CFG.autoDebuff && STATE.mp > 0.2 && (anyRareMonster() || STATE.monsters.length <= 2)) { if (hasSpell('Imperil')) {