Three whole categories of buff were missing, for two different reasons, and
both were my errors.
**Protections and weapon auras were silently dropped by the description
parser.** They are self-targeted and were sitting in the spellbook the whole
time, but retail words them differently and the pattern only accepted
"Increases the caster's X by N":
Fire Protection Self -> "Reduces damage the caster takes from Fire by 9%."
Armor Self -> "Increases the caster's natural armor by 20 points."
Aura of Blood Drinker -> "Increases a weapon's damage value by 2 points."
So the weapon and wand buffs do exist as self-cast "Aura of" lines and are now
cast. Each category has its own toggle, matching VTank's separate
BuffProfile_Prots and BuffProfile_Banes.
The underlying flaw mattered more than the two missing patterns: anything
unmatched was DISCARDED. It now falls into an Other bucket (off by default)
instead, so nothing self-targeted is lost without a word. A test caught a
second instance immediately -- regeneration spells say "Restores..." and were
vanishing the same way.
**Banes were excluded because I misread a flag.** I took IsSelfTargeted as
"can be cast on you". It means "needs no selection". Retail's own bane text
says exactly how they work:
"Increases a shield or piece of armor's resistance to slashing damage by
10%. Target yourself to cast this spell on all of your equipped armor."
So banes ARE cast on the person, and the catalogue now includes every
beneficial non-untargeted spell rather than only flagged self-casts, leaving
EvaluateGate to decide what a given target accepts. Before casting anything
without the self flag, MossTank selects the player -- and restores whatever
was selected before the pass, so targeting yourself does not quietly steal
the selection.
They are matched on retail's "Target yourself..." sentence rather than on the
word "Bane", so the classification comes from what the spell says it does.
Solution builds clean; 14,450 tests pass on the standard hermetic lane filter,
0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things from the first working buff pass.
**Pacing.** Casts were three seconds apart because of a fixed interval I added
before there was a real busy signal. There is one now -- the shared busy count,
incremented by the cast and decremented by the server's UseDone -- so the
interval is gone and the server is the only throttle. MossTank casts the moment
the previous action is acknowledged, which is the spam behaviour VTank has. The
stall timeout moved 25s -> 30s so a slow server does not read as a stall.
**Stamina to Mana was surprising.** VTank does convert vitals by default
(Recharge-*-Mana), so the behaviour is right, but a buff pass quietly spending
your stamina is not something to discover by watching. It is now a setting,
with its own thresholds, and can be turned off outright.
**Settings view.** Spell difficulty margin, rebuff time, the three vital
thresholds, and three toggles. Two details worth recording:
* The difficulty margin is SIGNED, per the VTank wiki: "a positive number
raises the skill necessary to cast spells, a negative number lowers it. To
attempt higher spells at a low level use a negative number." So the range
spans -100..+100 rather than starting at zero.
* It is a second registered panel with a complementary visible binding rather
than a tab control. Two panels and an Action need no new markup vocabulary,
and only one is ever on screen.
Adjuster buttons rather than typed fields: buttons are proven in plugin markup,
while an editable UiField would need keyboard routing plumbed through to plugin
panels first. Worth doing, but not as a side quest inside this change.
Also fixed: the App copy target names plugin files explicitly, so the new
markup would have been left out of the plugin directory and the settings panel
would have failed to load at runtime with the build perfectly green. Caught by
listing the output rather than trusting the build.
Solution builds clean; 14,438 tests pass on the standard hermetic lane filter,
0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reworks MossTank against user feedback and the Virindi Tank feature docs
(virindi.net is reachable again over https with a self-signed cert; the
research doc's "unreachable" note is stale).
VTank's stated default is the spec: "automatically buffs every Attribute and
Skill you have trained", and "all buff spells are recast when they go below 5
minutes". The previous pass buffed the whole spellbook and refreshed at 60s;
both are corrected.
The hard problem was working out WHICH stat each buff raises. The client's
spell table has no such link -- it arrives from the server with the
enchantment -- and the naming is too irregular to infer: Invulnerability
raises Melee Defense, Impregnability raises Missile Defense, Fealty raises
Loyalty, Sprint raises Run, Arcane Enlightenment raises Arcane Lore, and the
line called Willpower raises the attribute named Self. Any name-matching
scheme dies on that last one.
Retail states it outright in each spell's own description ("Increases the
caster's Life Magic skill by 10 points"), so BuffProfile derives the whole
mapping from shipped data at runtime. It also carries the one alias the data
needs: the spell text says "Assess Monster" where the skill table says "Assess
Creature", and without that the skill silently never matches.
Two data facts that would each have caused a real bug, found by dumping the
spell table rather than assuming:
* Family is NOT a spell-line identity in general. Retail groups the
instantaneous vital transfers by SOURCE vital, so family 89 holds both
"Stamina to Health" and "Stamina to Mana". Picking the strongest tier in a
family would convert into the wrong vital about half the time. Buff lines
group by family (correct for duration buffs, which is retail's own stacking
bucket); the conversions are found by name stem instead.
* Instantaneous spells have no duration and must be excluded from buff lines
entirely, or they are treated as buffs that never appear to land.
Tier selection now follows the character's skill in the casting school against
the spell's difficulty (VTank's SpellDiffExcessThreshold-Buff), which is why
PluginSpellInfo gained School as a SKILL id -- MagicSchool is retail's 1-5
school enum, not something a character trains.
Mana upkeep is the loop asked for: convert stamina to mana when mana is low,
Revitalize when that leaves stamina too low to convert, and refuse to drain
stamina past a floor. Unknown vitals read as zero and are treated as "no
information" rather than "empty", so it will not cast on a healthy character.
Panel no longer shows at character select. IsAvailable is now the runtime's
own lifecycle state rather than a proxy, and markup gained visible="{Binding}"
plus UiElement.VisibleSource -- evaluated before the visible gate, because
TickSelfAndChildren returns early when hidden and an element could otherwise
never un-hide itself.
Also: a generated SpellId enum of all 6,266 spells (tools/SpellDump --enum),
generated from portal.dat rather than copied, so it cannot drift and carries
no third-party licence; skill and spell names now come from the retail tables
for display; and the Buff click logs unconditionally, so "nothing happened"
can be told apart from "the click never arrived".
Solution builds clean; 14,433 tests pass on the standard hermetic lane filter,
0 failures, including 21 covering the buff profile, tier selection and mana
loop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>