Benchmark v3: 17 configs, chan-mp-focused wins (149 rounds)

Channeling experiments (Lv90, Nightmare, 5 runs each):
1. chan-mp-focused: 149.0 rounds (WINNER)
   - Mana gems at 70%, potions at 50% = stay MP-rich
   - Attack spells on for channeling kickstart
   - Buff refresh at < 15 turns
   - Spirit stance at OC 80, skills at OC 75
2. chan-no-buff-refresh: 143.0 (good, skip buff refresh chan)
3. chan-aggressive-buff: 142.4 (refresh buffs at < 20 turns)
4. chan-buff-only: 140.6 (no attack spells, buff refresh only)
5. conservative-items: 139.8 (old winner)

New defaults set to chan-mp-focused values.
This commit is contained in:
GaboGG 2026-07-21 14:12:54 -04:00
parent 0d57fead61
commit f060bd31a8
6 changed files with 319 additions and 153 deletions

View file

@ -29,15 +29,16 @@ const CFG = {
autoCure: true,
autoSpirit: false,
// — Thresholds
cureHP: 0.45, // Cast Cure below this HP (save items, use Cure)
cureItemHP: 0.50, // Use health items at this HP (conservative, let Cure carry)
// — Thresholds (benchmark-optimized)
cureHP: 0.45, // Cast Cure below this HP
cureItemHP: 0.50, // Use health items at this HP
cureRegenHP: 0.65, // Cast Regen below this HP
manaGemMP: 0.45, // Use mana gems below this MP (conservative)
manaPotionMP: 0.25, // Use mana potions at critical
manaGemMP: 0.70, // Use mana gems below this MP (aggressive, stay rich)
manaPotionMP: 0.50, // Use mana potions at this MP
spiritPotionSP: 0.30, // Use spirit gems/potions below this SP
spiritStanceOC: 80, // Activate Spirit Stance late (save SP for skills)
skillOC: 75, // Use weapon skills at this OC
useAttackSpells: true, // Use cheap spells for channeling kickstart
// — Out of battle
autoSell: true,

View file

@ -29,15 +29,16 @@ const CFG = {
autoCure: true,
autoSpirit: false,
// — Thresholds
cureHP: 0.45, // Cast Cure below this HP (save items, use Cure)
cureItemHP: 0.50, // Use health items at this HP (conservative, let Cure carry)
// — Thresholds (benchmark-optimized)
cureHP: 0.45, // Cast Cure below this HP
cureItemHP: 0.50, // Use health items at this HP
cureRegenHP: 0.65, // Cast Regen below this HP
manaGemMP: 0.45, // Use mana gems below this MP (conservative)
manaPotionMP: 0.25, // Use mana potions at critical
manaGemMP: 0.70, // Use mana gems below this MP (aggressive, stay rich)
manaPotionMP: 0.50, // Use mana potions at this MP
spiritPotionSP: 0.30, // Use spirit gems/potions below this SP
spiritStanceOC: 80, // Activate Spirit Stance late (save SP for skills)
skillOC: 75, // Use weapon skills at this OC
useAttackSpells: true, // Use cheap spells for channeling kickstart
// — Out of battle
autoSell: true,

View file

@ -25,145 +25,243 @@
"rounds": 1000,
"results": [
{
"name": "full-conservative",
"desc": "Late spirit, save items, no attack spells",
"avgRounds": "146.0",
"avgKills": "809.2",
"name": "chan-mp-focused",
"desc": "Channeling + aggressive mana gem use",
"avgRounds": "149.0",
"avgKills": "835.2",
"avgDmgRatio": "2.9",
"avgDmgPerRound": 4304,
"avgExp": 4488560,
"avgCredits": 58783,
"maxRounds": 162,
"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": 4485,
"avgExp": 4388800,
"avgCredits": 57381,
"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": 80920
"score": 77700
},
{
"name": "conservative-items",
"desc": "Save items, use Cure more",
"avgRounds": "142.8",
"avgKills": "793.8",
"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": 4468,
"avgExp": 4131520,
"avgCredits": 53230,
"maxRounds": 154,
"avgDmgPerRound": 4250,
"avgExp": 3854800,
"avgCredits": 52097,
"maxRounds": 162,
"survivalRate": "0",
"deaths": 5,
"score": 79380
"score": 75780
},
{
"name": "spirit-40",
"desc": "Spirit Stance at OC 40 (early)",
"avgRounds": "135.8",
"avgKills": "751.2",
"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
},
{
"name": "chan-always-regen",
"desc": "Channeling: always cast Regen first if not active",
"avgRounds": "134.2",
"avgKills": "739.8",
"avgDmgRatio": "3.4",
"avgDmgPerRound": 4457,
"avgExp": 3770560,
"avgCredits": 47867,
"maxRounds": 152,
"avgDmgPerRound": 4441,
"avgExp": 3694400,
"avgCredits": 51225,
"maxRounds": 158,
"survivalRate": "0",
"deaths": 5,
"score": 75120
},
{
"name": "spirit-80",
"desc": "Spirit Stance at OC 80 (late, skills first)",
"avgRounds": "128.8",
"avgKills": "703.6",
"avgDmgRatio": "3.6",
"avgDmgPerRound": 4398,
"avgExp": 3358160,
"avgCredits": 45813,
"maxRounds": 139,
"survivalRate": "0",
"deaths": 5,
"score": 70360
},
{
"name": "skills-at-60",
"desc": "Skills fire at OC 60 instead of 80",
"avgRounds": "126.4",
"avgKills": "689.6",
"avgDmgRatio": "3.6",
"avgDmgPerRound": 4356,
"avgExp": 3243600,
"avgCredits": 42872,
"maxRounds": 143,
"survivalRate": "0",
"deaths": 5,
"score": 68960
"score": 73980
},
{
"name": "baseline",
"desc": "Current v0.11.0 defaults",
"avgRounds": "126.4",
"avgKills": "688.8",
"avgDmgRatio": "3.8",
"avgDmgPerRound": 4340,
"avgExp": 3272880,
"avgCredits": 41772,
"maxRounds": 154,
"survivalRate": "0",
"deaths": 5,
"score": 68880
},
{
"name": "full-aggressive",
"desc": "Early spirit + aggressive items + attack spells",
"avgRounds": "126.0",
"avgKills": "688.8",
"avgRounds": "130.0",
"avgKills": "712.8",
"avgDmgRatio": "3.5",
"avgDmgPerRound": 4162,
"avgExp": 3215200,
"avgCredits": 41874,
"maxRounds": 141,
"avgDmgPerRound": 4371,
"avgExp": 3440240,
"avgCredits": 46973,
"maxRounds": 152,
"survivalRate": "0",
"deaths": 5,
"score": 68880
"score": 71280
},
{
"name": "balanced",
"desc": "Mid spirit, moderate items, attack spells for chan",
"avgRounds": "125.8",
"avgKills": "686.4",
"avgDmgRatio": "3.6",
"avgDmgPerRound": 4124,
"avgExp": 3198880,
"avgCredits": 42626,
"maxRounds": 138,
"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,
"survivalRate": "0",
"deaths": 5,
"score": 68640
},
{
"name": "aggressive-items",
"desc": "Use items very proactively",
"avgRounds": "117.0",
"avgKills": "631.4",
"avgDmgRatio": "4.0",
"avgDmgPerRound": 4390,
"avgExp": 2781760,
"avgCredits": 36607,
"maxRounds": 129,
"survivalRate": "0",
"deaths": 5,
"score": 63140
"score": 69860
},
{
"name": "attack-spells",
"desc": "Use cheap attack spells for channeling",
"avgRounds": "113.4",
"avgKills": "607.8",
"avgDmgRatio": "3.9",
"avgDmgPerRound": 4036,
"avgExp": 2602480,
"avgCredits": 36235,
"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",
"avgDmgRatio": "3.6",
"avgDmgPerRound": 4469,
"avgExp": 3200400,
"avgCredits": 41273,
"maxRounds": 137,
"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": 60780
"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
},
{
"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,
"survivalRate": "0",
"deaths": 5,
"score": 62360
}
],
"timestamp": "2026-07-21T18:00:29.218Z"
"timestamp": "2026-07-21T18:12:28.425Z"
}

View file

@ -111,6 +111,65 @@ const CONFIGS = [
// NOTE: skill OC threshold is hardcoded at 80 in strategy.js
// This compares the effect of that on the baseline
},
// ── EXPERIMENTAL: CHANNELING + BUFF OPTIMIZATION ──
{
name: 'chan-buff-only',
desc: 'Channeling via buff refresh only, no attack spells',
cureHP: 0.45, cureItemHP: 0.50, cureRegenHP: 0.65,
manaGemMP: 0.45, manaPotionMP: 0.25, spiritPotionSP: 0.30,
spiritStanceOC: 80, useAttackSpells: false,
skillOC: 75,
},
{
name: 'chan-aggressive-buff',
desc: 'Channeling via buffs at 20+ turns, keep all buffs fresh',
cureHP: 0.45, cureItemHP: 0.50, cureRegenHP: 0.65,
manaGemMP: 0.45, manaPotionMP: 0.25, spiritPotionSP: 0.30,
spiritStanceOC: 80, useAttackSpells: false,
skillOC: 75,
channelRefreshAt: 20,
},
{
name: 'chan-always-regen',
desc: 'Channeling: always cast Regen first if not active',
cureHP: 0.45, cureItemHP: 0.50, cureRegenHP: 0.90,
manaGemMP: 0.45, manaPotionMP: 0.25, spiritPotionSP: 0.30,
spiritStanceOC: 80, useAttackSpells: false,
skillOC: 75,
},
{
name: 'chan-cheap-spell',
desc: 'Channeling via cheapest attack spell (4 MP)',
cureHP: 0.45, cureItemHP: 0.50, cureRegenHP: 0.65,
manaGemMP: 0.45, manaPotionMP: 0.25, spiritPotionSP: 0.30,
spiritStanceOC: 80, useAttackSpells: true,
skillOC: 75,
},
{
name: 'chan-mp-focused',
desc: 'Channeling + aggressive mana gem use',
cureHP: 0.45, cureItemHP: 0.50, cureRegenHP: 0.65,
manaGemMP: 0.70, manaPotionMP: 0.50, spiritPotionSP: 0.30,
spiritStanceOC: 80, useAttackSpells: true,
skillOC: 75,
},
{
name: 'chan-spirit-60',
desc: 'Channeling + spirit stance at 60',
cureHP: 0.45, cureItemHP: 0.50, cureRegenHP: 0.65,
manaGemMP: 0.45, manaPotionMP: 0.25, spiritPotionSP: 0.30,
spiritStanceOC: 60, useAttackSpells: true,
skillOC: 80,
},
{
name: 'chan-no-buff-refresh',
desc: 'Only cast missing buffs, never refresh early',
cureHP: 0.45, cureItemHP: 0.50, cureRegenHP: 0.65,
manaGemMP: 0.45, manaPotionMP: 0.25, spiritPotionSP: 0.30,
spiritStanceOC: 80, useAttackSpells: true,
skillOC: 75,
noBuffRefreshChannel: true,
},
];
const RUNS_PER_CONFIG = 5;

View file

@ -1,4 +1,4 @@
// ── Strategy decision engine (mirrors the userscript's logic) ──
// ── Strategy decision engine (mirrors the userscript's Adept strategy) ──
const RARE_NAMES = [
'manbearpig', 'white bunneh', 'mithra', 'dalek',
@ -14,6 +14,7 @@ const BUFF_PRIORITY = [
{ icon: 'spark_of_life', spell: 'Spark of Life', minTurns: 1 },
{ icon: 'regen', spell: 'Regen', minTurns: 2 },
{ icon: 'absorb', spell: 'Absorb', minTurns: 2 },
{ icon: 'shadowveil', spell: 'Shadow Veil', minTurns: 3 },
];
function hasAnyRare(monsters) {
@ -21,7 +22,6 @@ function hasAnyRare(monsters) {
}
// Returns an action object: { type, name?, target? }
// type: 'attack' | 'spell' | 'skill' | 'item' | 'toggle_spirit'
function decideAction(player, monsters, cfg) {
// ── 0. Mystic Gem for channeling ──
if (!player.channeling) {
@ -52,16 +52,15 @@ function decideAction(player, monsters, cfg) {
// ── 3. Buffs ──
for (const b of BUFF_PRIORITY) {
const dur = player.buffDuration(b.icon);
if ((dur === 0 || dur < b.minTurns) && player.hasSpell(b.spell) && player.mp > 0.15 * player.maxMp) {
if ((dur === 0 || dur < b.minTurns) && player.mp > 0.2 * player.maxMp && player.hasSpell(b.spell)) {
if (b.icon === 'regen' && player.hp / player.maxHp >= cfg.cureRegenHP) continue;
return { type: 'spell', name: b.spell };
}
}
// ── 4. Debuffs ── (only on important fights)
// ── 4. Debuffs ──
const importantFight = hasAnyRare(monsters) || monsters.filter(m => m.isAlive()).length <= 2;
if (importantFight && player.mp > 0.2 * player.maxMp) {
// Find strongest alive
const alive = monsters.filter(m => m.isAlive());
if (alive.length > 0) {
const strongest = alive.reduce((a, b) => a.hp > b.hp ? a : b);
@ -72,34 +71,36 @@ function decideAction(player, monsters, cfg) {
}
}
// ── 5. Spirit Stance ──
if (player.oc >= cfg.spiritStanceOC && !player.spiritStance && player.sp > 0.10 * player.maxSp)
return { type: 'toggle_spirit' };
if (player.sp < 0.03 * player.maxSp && player.spiritStance)
return { type: 'toggle_spirit' };
// ── 6. Weapon skills ──
// ── 5. Weapon skills (before spirit stance) ──
const aliveCount = monsters.filter(m => m.isAlive()).length;
const skillOC = cfg.skillOC || 80;
const skillOC = cfg.skillOC || 75;
if (player.oc >= skillOC) {
// Great Cleave: bosses only
if (hasAnyRare(monsters) && player.hasSkill('Great Cleave') && !player.cooldowns['Great Cleave'])
return { type: 'skill', name: 'Great Cleave', target: monsters.filter(m => m.isAlive())[0] };
// Rending Blow: 5+ enemies
if (aliveCount >= 5 && player.hasSkill('Rending Blow') && !player.cooldowns['Rending Blow'])
// Rending Blow: AoE vs 5+
if (aliveCount >= 5 && player.hasSkill('Rending Blow') && player.cooldowns['Rending Blow'] === undefined)
return { type: 'skill', name: 'Rending Blow', target: monsters.filter(m => m.isAlive())[0] };
// Shatter Strike: 5+ enemies, needs Penetrated Armor
if (aliveCount >= 5 && player.hasSkill('Shatter Strike') && !player.cooldowns['Shatter Strike']) {
const hasArmorBreak = monsters.some(m => m.hasDebuff('penetrated_armor'));
if (hasArmorBreak)
// Shatter Strike: AoE vs 5+ with Penetrated Armor
if (aliveCount >= 5 && player.hasSkill('Shatter Strike') && player.cooldowns['Shatter Strike'] === undefined) {
if (monsters.some(m => m.hasDebuff('penetrated_armor')))
return { type: 'skill', name: 'Shatter Strike', target: monsters.filter(m => m.isAlive())[0] };
}
// Great Cleave: bosses only
if (hasAnyRare(monsters) && player.hasSkill('Great Cleave') && player.cooldowns['Great Cleave'] === undefined)
return { type: 'skill', name: 'Great Cleave', target: monsters.filter(m => m.isAlive())[0] };
}
// ── 6. Spirit Stance ──
if (player.oc >= 60 && !player.spiritStance && player.sp > 0.40 * player.maxSp && !player._spiritCooldown) {
player._spiritCooldown = 3;
return { type: 'toggle_spirit' };
}
if (player.sp < 0.03 * player.maxSp && player.spiritStance && !player._spiritCooldown) {
player._spiritCooldown = 3;
return { type: 'toggle_spirit' };
}
if (player._spiritCooldown > 0) player._spiritCooldown--;
// ── 7. Damage spells ──
if (cfg.useAttackSpells && player.mp > 0.15 * player.maxMp) {
if (cfg.useAttackSpells && player.mp > 0.2 * player.maxMp) {
const dmg = player.spells.find(s => SPELL_T1.includes(s.n));
if (dmg) {
const alive = monsters.filter(m => m.isAlive());
@ -109,19 +110,24 @@ function decideAction(player, monsters, cfg) {
// ── 8. Channeling kickstart ──
if (!player.channeling) {
// Refresh expiring buffs
const refreshAt = cfg.channelRefreshAt || 15;
// Refresh expiring buffs (or missing ones)
for (const b of BUFF_PRIORITY) {
const dur = player.buffDuration(b.icon);
if (dur > 0 && dur < 15 && player.mp > 0.15 * player.maxMp && player.hasSpell(b.spell)) {
if (dur > 0 && dur < refreshAt && player.mp > 0.15 * player.maxMp && player.hasSpell(b.spell)) {
if (b.icon === 'regen' && player.hp / player.maxHp >= cfg.cureRegenHP) continue;
return { type: 'spell', name: b.spell };
}
}
// Cast missing buffs
for (const b of BUFF_PRIORITY) {
if (player.buffDuration(b.icon) === 0 && player.mp > 0.15 * player.maxMp && player.hasSpell(b.spell)) {
if (b.icon === 'regen' && player.hp / player.maxHp >= cfg.cureRegenHP) continue;
return { type: 'spell', name: b.spell };
// Cast missing buffs (skip if noBuffRefreshChannel is set)
if (!cfg.noBuffRefreshChannel) {
for (const b of BUFF_PRIORITY) {
if (player.buffDuration(b.icon) === 0 && player.mp > 0.15 * player.maxMp && player.hasSpell(b.spell)) {
if (b.icon === 'regen' && player.hp / player.maxHp >= cfg.cureRegenHP) continue;
return { type: 'spell', name: b.spell };
}
}
}
}
@ -133,4 +139,4 @@ function decideAction(player, monsters, cfg) {
return null;
}
module.exports = { decideAction, RARE_NAMES };
module.exports = { decideAction, RARE_NAMES, BUFF_PRIORITY };

View file

@ -14,15 +14,16 @@ const CFG = {
autoCure: true,
autoSpirit: false,
// — Thresholds
cureHP: 0.45, // Cast Cure below this HP (save items, use Cure)
cureItemHP: 0.50, // Use health items at this HP (conservative, let Cure carry)
// — Thresholds (benchmark-optimized)
cureHP: 0.45, // Cast Cure below this HP
cureItemHP: 0.50, // Use health items at this HP
cureRegenHP: 0.65, // Cast Regen below this HP
manaGemMP: 0.45, // Use mana gems below this MP (conservative)
manaPotionMP: 0.25, // Use mana potions at critical
manaGemMP: 0.70, // Use mana gems below this MP (aggressive, stay rich)
manaPotionMP: 0.50, // Use mana potions at this MP
spiritPotionSP: 0.30, // Use spirit gems/potions below this SP
spiritStanceOC: 80, // Activate Spirit Stance late (save SP for skills)
skillOC: 75, // Use weapon skills at this OC
useAttackSpells: true, // Use cheap spells for channeling kickstart
// — Out of battle
autoSell: true,