- Was using vrhd (HP element) instead of vrm (current MP)
- Now reads #vrm textContent for current MP value
- Simply compares mpCost > curMp to skip unaffordable spells
- This prevents the infinite Heartseeker loop when you
don't have enough MP even though the buff is a good target
- Removed SPELL_COSTS hardcoded table
- Base MP costs are now read from the magic pane's onmouseover
tooltip at battle init and cached in STATE._baseMpCosts
- Cache is populated during parseBattleState() and persists
for the entire battle, avoiding channeling's 1-MP display
- Cache resets on new battle, so level-ups or stat changes
are picked up when starting a new arena
- Removed hardcoded SPELL_COSTS table (was brittle)
- Now reads base MP cost directly from the magic pane's
onmouseover tooltip (always shows true base cost, unaffected
by channeling's 1-MP display or Mana Conservation)
- Added affordable check: skip spells where base cost exceeds
current MP, even though channeling would make it cost 1
- The game's native MP check (shown by "You do not have enough")
checks against base cost, not channeling-reduced cost
- Some spells show mp=1 in the DOM due to high Mana Conservation
proficiency, making fill-level scoring produce near-zero values
- Now: if mpCost < 5, look up the base cost from SPELL_COSTS table
- This ensures Heartseeker (141 MP) scores correctly even when the
DOM shows it costing 1 MP
- Magic pane query used '.btsd[onclick]' which excludes
on-cooldown spells (opacity:0.5, no onclick attribute)
- Heartseeker was invisible to hasSpell() during its cooldown
period, which is exactly when channeling procs from it
- Changed to '.btsd' with onmouseover fallback
- Logs the top candidate from findBestChannelingTarget() on every Q press
- Shows: spell, score, dur, maxDur, mpCost, emptiness%
- Next time channeling doesn't fire on a buff, the console will show
the actual computed scores so we can see what's happening
- When maxBuffDur for a buff equals its current duration (first observation
in a battle), the emptiness was 0 — making the script think all buffs
are full and skipping them
- Now: if maxDur <= current dur, assume a minimum of 30 turns as the max
- This ensures buffs like Shadow Veil at 8/8 are treated as 8/30 (73% empty)
instead of 8/8 (0% empty)
- The regex only matched 'X turns remaining' text format
- Buffs like channeling and freshly cast spells use the raw third
parameter of set_infopane_effect('Name','desc',X) without 'turns rem'
- These were defaulting to 50 turns, making all fresh buffs appear
as having 50 turns when they really had 0-5
- Added fallback to parse the last numeric argument of the function call
- Mystic Gem usage now tracked with STATE._mysticUsed flag
Prevents re-triggering the gem check after it's been consumed
(stale STATE.itemsKnown was causing infinite 'item → p' loops)
- Flag resets on new battle initialization
- Added buff durations + channeling status to Q-key console output
Shows: ch=true/false mp=X% oc=X buffs: heartseeker=47 haste=24 ...
- Removed call to undefined finalizeBattleLog() that was spamming
ReferenceErrors and potentially corrupting the MutationObserver
- Raised channeling score threshold from 5 to 30: now only picks a
buff for channeling if it's at least 30% empty on a 100-MP spell
or 60% empty on a 50-MP spell
- Spark at 30/60 turns (50% empty, score 44) now falls to cheap
damage fallback instead of wasting channeling
- Channeling detection was ONLY from battle log text entries
- When log entries scroll off (new round starts), the 'gains the effect'
message is lost and STATE.channeling stays false permanently
- Added effects pane check: if channeling.png is visible, force channeling=true
- This fixes the root cause of channeling being ignored when the log
has scrolled past the proc message
- Also bumped version properly this time
- Score = emptiness_ratio × mp_cost
- emptiness_ratio = (max_dur - current) / max_dur
- max_dur is tracked dynamically per buff in STATE.maxBuffDur
(learned from the highest duration seen per icon)
- Freshly cast buffs score ~0 regardless of MP cost
- A 100-MP buff at 1/90 turns scores 98.9
- A 40-MP buff at 25/50 turns scores 20.0
- A 140-MP buff at 71/80 turns scores 15.75
- Removed the Heartseeker >50 skip (no longer needed - fill level handles it)
- Reverted the >10 turns cutoff (wrong approach)
- Instead, added a minimum score threshold of 5 to the best candidate
- At score >= 5: a 50-MP spell with <9 turns, a 140-MP spell with <27 turns
- Below score 5: falls through to cheap damage spell fallback
- This prevents recasting fresh buffs while still considering all spells
- findBestChannelingTarget: skip buffs with >10 remaining turns
- When all buffs are fresh, falls through to cheap damage spell
instead of wasting channeling on a nearly-full duration buff
- This prevents recasting Spark of Life at 15 turns just because
its score happens to be highest among all fresh buffs
- The vitals observer check 'battleLog && !battleLog.summary' referenced
an undefined variable, throwing on every observer tick
- Simplified to just check for pane_completion existence
- This crash may have been interfering with state parsing on the observer
- config.js: 0.13.0 -> 0.13.8
- header.user.js: 0.12.0 -> 0.13.8 (was never bumped past 0.12.0)
- Now the @version tag in the userscript header matches the internal VERSION
- Reduced buff loop MP threshold from 20% to 10% in all three tiers
(prevents skipping buffs when MP is moderately low)
- Also fixed the 'battleLog is not defined' ReferenceError from the
vitals observer (removed in previous commit but was still referenced)
- Moved findBestChannelingTarget() BEFORE the BUFF_PRIORITY loop
- When Q is pressed and channeling is active, the script now picks the most
expensive buff for the channeling charge instead of wasting it on the
first buff in priority order (Haste at 32 MP instead of Heartseeker at 175)
- Reverted the vitals auto-execute (Q-key workflow, not auto-battle)
- All three tiers: channeling check fires after castInitialBuffs but
before the regular shouldBuff loop
- Added action execution to the vitals MutationObserver so the script
fires on every round tick, not only when hovering a monster
- This fixes buffs expiring and channeling being wasted when the user
isn't actively hovering a monster during auto-battle
- Removed unused STATE.battleRound
- findBestChannelingTarget() fallback: expanded damage spell list to
include T2 spells (Inferno, Blizzard, etc.) and a last-resort loop
that fires ANY offensive spell the player knows
- Prevents channeling from expiring when all buffs have high remaining
durations and the player has no T1 elemental spells
- Added castInitialBuffs() that fires when 60%+ buffs are down (first round)
- Without channeling: casts cheapest buff first (more casts = more procs)
- With channeling: casts most expensive remaining buff (best value for free cast)
- Added isFirstRoundBuffs() helper: checks if most buffs are missing
- Injected into all three tiers (Novice, Adept, Veteran) before the
regular BUFF_PRIORITY loop
- Added STATE.battleRound counter for future round tracking
- New settings-page.js: adds a toolbar above the auto-salvage table
on Character Settings (ss=se) with bulk preset buttons
- Preset 1: Sell <=Fair (value 2) / Salvage <=Average (value 3)
- Preset 2: Sell <=Crude (value 1) / Salvage <=Fair (value 2)
- Disable All: reset both to No Auto-Sell/Salvage
- Shows current state summary (Sell X | Salvage Y)
- Also fixes init.js and build.sh to include new file
- Scrap the old floating panel approach (wrong DOM selectors)
- Now injects a toolbar directly into the equipment list on the Armory page
- Parses equipment from native #equiplist table rows using correct
DOM selectors (hover_equip, select_equip, eqids[] checkboxes)
- Detects current screen (organize/sell/salvage/modify) to show relevant
bulk-select buttons per tab
- Sell tab: select Crude, select <=Fair
- Salvage tab: select salvage-grade, select <=Average
- Organize tab: select equipped (for pinning)
- Plus: Select All, Invert, Clear on every screen
- Grades by quality: Keep (Magnificent+), Keep (Superior),
Salvage (Average-Fair), Sell (Crude)
- Resets properly across page navigations (single-use id guard)
- Removed enhanceEquipShop() — broken bulk sell/salvage from equip shop page
- Rewrote enhanceArmory() with actionable equipment management panel:
- Parses all equipment from the Armory (ss=am) page
- Grades each piece by quality: Keep (Magnificent+), Keep (Superior),
Salvage (Average-Fair), Sell (Crude)
- Bulk Sell / Bulk Salvage buttons that click native game buttons
- Shows slot separators, quality colors, status icons (equipped/locked/stored)
- Sorting: equipped/pinned first, then by quality, then by level
- Summary counts for each action category
At Lv100+ / Hell difficulty, all normal monsters also have SP bars
(the red nbarred.png). This made the script think every monster was
a boss, triggering debuffs (Imperil/Weaken) on every fight and
draining MP.
Changes:
- isRareMonster() no longer checks for nbarred.png
- Only gold border (BD7400/E6CCA3) indicates a real boss
- Debuffs now only fire on gold-border bosses, not on ≤2 monsters
Spirit item logic split by source:
- P-slot gem (10007): used immediately on every turn, free to refill
with crystals, keeps SP permanently high for Spark + Stance
- Spirit Draughts/Potions in item slots: only used when SP < threshold (30%)
to conserve consumable credits
Spirit gems now fire whenever one is slotted regardless of SP level.
This keeps SP constantly topped up for:
- Spark of Life (costs 50% SP to trigger)
- Manual Spirit Stance (costs 1 SP + 10% OC per round)
Removed the old SP < 30% threshold check.
Spark of Life consumes 50% of your SP bar when triggered. If you're in
Spirit Stance with low SP, Spark fails to proc. Changes:
- No auto-activation of Spirit Stance (toggle manually with S key)
- Auto-disable if SP drops below 65% to reserve SP for Spark trigger
- Veteran still has optional auto-activation via CFG.autoSpirit but
requires SP > 65% (practically never triggers)
The active buff icon is /y/e/sparklife.png -> stored as 'sparklife'.
The 'fallenshield.png' check is for detecting when it has been consumed
(triggered), not for the active buff. This was causing the script to
think Spark of Life was never active, recasting it every turn.
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
Channeling works: proc on mana-costing spell -> next spell costs 1 MP
and is 50% stronger/longer. The charge lasts 5 ticks (15 with Mystic
Gem) then wears off and is wasted.
Previously the script would basic attack with channeling charged,
wasting the bonus. Now:
- If channeling is active and nothing more urgent, still cast a buff
(refresh anything under 10 turns) or a cheap damage spell
- Never basic attack while channeling is charged
- Novice/Adept/Veteran all have this protection
Channeling wiki fix:
- Procs randomly on any mana-costing spell
- Proc chance = spell_cost / (base_mana * 1.2)
- Higher cost = higher chance (Regen/Absorb at 17 MP = ~6%)
- Cannot re-proc while already channeling
- Duration: 5 ticks (15 with Mystic Gem)
- Effect: next spell costs 1 MP, 50% stronger/longer
Kickstart now only refreshes expiring buffs (missing or < 5 turns)
with decent-cost spells (Regen/Absorb). No more cheap attack spells
at 4 MP = 1.4% proc chance — that was a waste of MP.
Defaults reverted to full-conservative values since the 'mp-focused'
win was based on wrong channeling assumptions.
Shadow Veil buff icon is stored as 'shadowveil' (from img src filename,
no underscore). Priority list now uses the correct key. minTurns=3
prevents recasting while still active. Also moved to last priority
position — only cast when MP is plentiful.
At 20% SP (~10 SP = 10 rounds of sustain), the player would toggle on
during a Grindfest round, consume SP quickly, toggle off when SP < 3%,
then 3 turns later the cooldown expires and OC is back to 60+ -> toggle
on again. Raising the threshold to 40% means ~20 rounds of sustained
Spirit Stance, making the engagement meaningful and rarely cycling.
Spirit Stance costs 1 SP + 10% OC per round (wiki). Previously toggled
on at 10% SP which meant ~5 rounds of sustain, often running out
immediately. Now requires 20% SP (~10 rounds sustain) to toggle on.
3-turn cooldown still prevents rapid flip-flopping between states.
Spirit stance was toggling on/off every turn because conditions (OC >= 60,
SP > 10%) would be met immediately after toggling off. Added a 3-turn
cooldown via STATE._spiritCooldown that blocks re-toggling until it
expires. Prevents wasted turns on spirit stance spam.
- 23 source files in src/ (build via scripts/build.sh)
- Forum-sourced player knowledge in references/
- DESIGN.md with architecture and corrections
- References to existing scripts (Monsterbation, jpx, HV Utils)