acdream/docs/research/2026-08-26-mosstank-vtank-utilitybelt-research.md

23 KiB
Raw Blame History

MossTank research: Virindi Tank parity and UtilityBelt expressions

Date: 2026-08-26

This report is the requirements baseline for turning MossTank from the small self-buffing sample into acdream's full automation plugin. The product target is deliberately broad: all Virindi Tank functionality, with UtilityBelt's more capable expression dialect as the scripting baseline. File compatibility is a separate decision; behavioral capability is not.

1. Evidence and limits

The following primary VTank pages were read and cross-checked (the live wiki and its indexed historical revisions were both used where a mirror was temporarily unavailable):

  • http://virindi.net/wiki/index.php/Virindi_Tank
  • http://virindi.net/wiki/index.php/Virindi_Tank_Standard_Options
  • http://virindi.net/wiki/index.php/Virindi_Tank_Advanced_Options
  • http://virindi.net/wiki/index.php/Virindi_Tank_Commands
  • http://virindi.net/wiki/index.php/Virindi_Tank_Meta_System
  • http://virindi.net/wiki/index.php/Meta_Expressions
  • https://utilitybelt.gitlab.io/docs/expressions/

The 2026-08-27 MT2 follow-up also verified the exact documented distinctions that drive the combat scheduler:

  • A+R requires MinimumRingTargets inside Ring Range; R without A rings with any configured target inside range and falls back to standard war outside;
  • UseArcs prefers an arc over a bolt only at/above ArcRange;
  • Void Basic, Drain Auto, and Harm are distinct Monsters damage choices;
  • GhostMonsterSpellAttemptCount counts spell attempts which never start, while BlacklistMonsterAttemptCount counts successful attacks which miss;
  • the health-tracker ghost detector is independent and applies to melee, missile, and magic.

Sources: the official Virindi_Tank_Standard_Options, Virindi_Tank_Advanced_Options, Virindi_Tank_FAQ, Options_List, and Virindi_Tank_Changelog pages listed above.

For UtilityBelt, documentation was checked against the primary source rather than relying on the generated web page alone. The inspected repository was https://gitlab.com/utilitybelt/utilitybelt, commit 5fe9825a82f38047737768fd92c61dd47d88e467 (2026-03-05). The grammar is UtilityBelt/Lib/Expressions/MetaExpressions.g4; every method carrying an ExpressionMethod attribute was enumerated. The source is MIT licensed.

This report extends, rather than replaces, 2026-07-29-vtank-plugin-automation-requirements.md. That earlier report already decoded VTank's .met, .nav, and .utl structures from primary sources and remains the format reference.

2. Complete VTank capability map

2.1 Combat

VTank is a priority-driven combat controller, not merely an auto-attack loop. Its supported combat family includes:

  • melee, missile, mage, hybrid, two-handed, Void and Summoning characters;
  • Life harm/martyr attacks, grenades, lenses, cast-on-strike weapons, streaks;
  • automatic damage/weapon choice and monster-specific weapon, offhand and pet element overrides;
  • monster rules with DEFAULT plus ordered first-match expressions;
  • per-rule priority from ignore (-1) through 4, attack/debuff flags, damage type, attack height, ring/streak choices, and void curses;
  • target selection by distance, angular deviation, or the hybrid method using angle inside a configurable cutoff and distance outside it;
  • target lock, blacklist/retry behavior, and ghost-target retirement after failed casts or missing health updates;
  • debuff scheduling by one target, priority group, or all targets before attack; spell-level versus skill-based debuff choice and reapply windows;
  • automatic ring use by nearby-target density and arc/bolt choice by range;
  • melee high/middle/low attacks, automatic or explicit power, Recklessness;
  • pet density, element, refill and test behavior.

VTank's macro scheduler checks multiple action lists in priority order. Combat therefore cannot be implemented as an isolated timer: healing, buffing, navigation, looting, fellowship assistance and combat all need one arbiter.

2.2 Buffing and vitals

  • automatic trained attribute/skill buffs, protections, banes, auras, regeneration and configured extra buffs;
  • protection/bane profile sets, exclusions, level/tier selection, signed skill-over-difficulty thresholds, force buff and idle top-off;
  • time-remaining rebuff and persisted item-buff duration knowledge;
  • combat, idle and fellowship-helper vital thresholds;
  • kits, vital transfers, post-switch recharge behavior and special healing items;
  • self-dispel in response to high-level vulnerabilities.

MossTank's current buff engine already owns the first useful subset: known self buffs, tier/difficulty choice, in-force enchantment timing, force buff, and stamina/mana upkeep. It remains plugin policy over host primitives.

2.3 Inventory and crafting

  • AutoStack and AutoCram;
  • pea splitting and priority rules;
  • crafting of kits, foods, arrowheads and special ammunition;
  • mana-stone acquisition, filling and application to equipped items;
  • lockpick selection and use;
  • component, consumable, tool and ammunition upkeep.

2.4 Looting

  • corpse approach/open/retry/timeout/blacklist;
  • all/fellow/rare loot modes and priority boosts;
  • appraisal/ID wait, unknown-scroll reading and salvage combining;
  • a loot-plugin seam, with VTClassic as the canonical ordered, first-match rule engine over raw and computed item properties;
  • actions including no-loot, keep, keep-up-to, salvage, sell, read and custom user actions.

The host must expose object property bags, appraisal completion and transaction primitives. Rule ordering and loot-profile policy belong in MossTank.

2.5 Navigation

  • circular, linear, once/runback and follow routes;
  • points, portals, recalls, pauses, chat, vendor, repeated NPC talk/use, server-confirmed checkpoints and charged/shift/strafe jumps;
  • closest-entry, reversal, arrival/off-course ranges, door use and follow-around-corners;
  • combat/nav priority interaction.

Route storage belongs to the plugin. The host owes move-to, follow, turn, jump, use and authoritative-arrival primitives.

Direct inspection of the official assembly on 2026-08-27 pinned the route contract more tightly:

  • eNavType is Circular, Linear, Target and Once; Once destructively removes its first completed row and Linear deliberately visits each endpoint once while flipping direction;
  • eWaypointType assigns Point/Portal/Recall/Pause/ChatCommand/OpenVendor/ Portal2/UseNPC/Checkpoint/Jump to numeric ids 0..9;
  • fd.cs turns outside 4°, moves while turning only within 45° beyond 3 m or 15° inside 3 m, and stops at NavCloseStopRange (default 2 m);
  • gr.cs compares the checkpoint against the last server position rather than client prediction and nudges forward after 15 seconds without an acknowledgement;
  • gl.cs records the followed player's path by approximately 9.6 cm and drops old breadcrumbs when the follower comes within 2.4 m of a later path segment, preserving follow-around-corners;
  • e9.cs and fa.cs reacquire exact-name/class objects within 2.5 m of the stored position. Portal2 retries when portal exit remains within 15 m of its origin; UseNPC repeats until the named NPC tells or gives to the player;
  • b7.cs is a rule independent of the route node list. OpenDoors defaults false; it IDs doors at 20 m, opens at 4 m, and accepts a lock when Lockpick is at least difficulty - 50 using an owned lockpick.

These are plugin policies over additive canonical projections, not a second movement model. The host applies semantic movement intent through Runtime's existing command interpreter and supplies the accepted server position needed only by Checkpoint.

2.6 Fellowship and social automation

  • tell-driven recruitment and waiting lists;
  • fellowship leader/member/state queries and leader replacement voting;
  • fellowship healing, corpse permissions and coordinated target/debuff policy;
  • multi-client composition through chat rather than a privileged macro API.

External loot-classifier seam

VTank loads one LootPluginBase, asks DoesPotentialItemNeedID, and then calls GetLootDecision(GameItemInfo). Its public result vocabulary is NoLoot, Keep, Salvage, Sell, Read, User1User5 and KeepUpTo with Data1 as the limit. MossTank modernizes discovery into a host-owned classifier registry: plugins register a namespaced classifier for their own lifetime, while MossTank remains the corpse/appraisal/pickup/action executor. The selected engine is durable policy; if it unloads, MossTank returns no classifier match instead of silently applying the built-in profile.

Direct inspection of the official hv.cs also shows that a custom loot plugin's per-item action is retained only after the item enters owned inventory and is removed when the item leaves. The modern registry therefore has matching OnLooted and OnItemRemoved callbacks. MossTank invokes them only from authoritative inventory publication/removal, never when pickup is merely dispatched.

Options-page scheduler findings

The official Options controls are not merely presentation aliases:

  • fz.cs runs the ordinary RebuffTimeRemainingSeconds rule before combat;
  • cLogic.cs runs a second IdleBuffTopoffTimeSeconds pass only behind IdleBuffTopoff, after attack/loot work has gone idle;
  • the PRETARGETAPPROACH g8 rule navigates only between AttackDistance and ApproachDistance, and requires both combat and navigation to be enabled;
  • cm.cs changes to Peace only as the final no-target/no-work fallback.

The UI displays AC-distance settings multiplied by 240. MossTank stores metres in its typed controllers and converts only at the VTank option boundary.

2.7 Meta state machine

  • named states beginning at Default;
  • state-local rules, each firing once per state entry;
  • nested conditions (All, Any, Not) and conditions for chat regex, inventory, timers, nav state, death, vendors, monsters, buffs, coordinates, portals, burden, route distance, expressions and captured chat groups;
  • actions for state transition, chat, grouped actions, embedded navigation, call/return stack, expression execution, expression-derived chat, watchdogs, option read/write and runtime-created views;
  • a roughly 293 ms decision cadence plus evaluation when the macro asks for its next action.

2.8 Profiles, commands and companion behavior

  • independent settings, navigation, loot and meta profiles; global and per-character variants; hot loading and automatic persistence;
  • command parity for macro state, options, buffing, meta, item testing, property dumps, monster/spell diagnostics, route editing, attack power and debug output;
  • extensibility equivalent to VTClassic, VI2, item tools, follower/status HUD, alerts and cross-character inventory. Some belong as separate acdream plugins, but MossTank's API must permit them without privileged host code.

3. Expression language target

3.1 Why UtilityBelt is the baseline

VTank expressions are enough to power classic metas, but UtilityBelt preserves the familiar syntax while adding typed lists and dictionaries, slicing, higher-order collection functions, broader object queries and more action primitives. MossTank should implement the UtilityBelt-compatible semantic superset and offer a VTank compatibility mode for old expressions.

3.2 Grammar and evaluation semantics

The audited UtilityBelt grammar supports:

  • multiple ;-separated statements, returning the final result;
  • session ($), persistent (@) and global (&) variables;
  • decimal and hexadecimal numbers, booleans and two string forms;
  • function calls using name[...];
  • typed values: number, string, boolean, list, dictionary, coordinate, world object, stopwatch and UI control;
  • list/string/dictionary indexing, slices and negative indices;
  • complement, shifts, bitwise operators, exponentiation, arithmetic, regex match (#), comparison, short-circuit && and ||;
  • registered function metadata, arity/type validation and documented return types;
  • collection creation/mutation/copying plus map/filter/reduce/sort/range.

Implementation requirements follow directly: parse into an immutable AST; compile or interpret without ambient reflection; use explicit value kinds; short-circuit logical nodes; attach cancellation and an instruction budget; make all world/action functions capabilities supplied by the MossTank engine; and serialize only persistent/global variable stores.

3.3 Audited UtilityBelt function catalog (260 declarations)

The declaration count includes aliases/overloads. Grouped by capability, the public names are:

  • language/conversion/math: abs, acos, asin, atan, atan2, ceiling, chr, cnumber, cos, cosh, cstr, cstrf, floor, hexstr, iif, ifthen, isfalse, istrue, lumavg, lumtotal, ord, randint, round, sin, sinh, sqrt, strlen, tan, tanh, tostring, vitae;
  • variables: getvar, setvar, testvar, touchvar, clearvar, clearallvars and the corresponding pvar and gvar families;
  • execution/chat: exec, delayexec, clearexec, echo, chatbox, chatboxpaste;
  • lists: listcreate, listadd, listinsert, listremove, listremoveat, listgetitem, listcontains, listindexof, listlastindexof, listcopy, listreverse, listpop, listcount, listclear, listfilter, listmap, listreduce, listsort, listfromrange;
  • dictionaries: dictcreate, dictgetitem, dictadditem, dicthaskey, dictremovekey, dictkeys, dictvalues, dictsize, dictclear, dictcopy;
  • time/location: getdatetimelocal, getdatetimeutc, getunixtime, getworldname, getplayercoordinates, getplayerlandblock, getplayerlandcell, coordinate parse/get/distance/string functions, stopwatch functions, and the eleven getgame*/day/night functions;
  • character: raw typed property reads, base/buffed skills, training level, base/current/buffed-max vitals, base/buffed attributes, burden, free slots, cooldown expiration, account hash and character index;
  • spells/components: getknownspells, getisspellknown, getcancastspell_buff, getcancastspell_hunt, getspellexpiration, getspellexpirationbyname, spelldata, spellname, componentdata, componentname;
  • world objects: validity/data/ID-time, raw typed properties, identity, health/vitals, spells, coordinates, selection/player/open-container, door state, nearest monster/door/by class/name/template, and wobjectfindall* variants over world, landscape, inventory and containers;
  • actions: select, use, apply, give, equip wand, cast, cast-on-target, move, split and drop;
  • combat/movement: combat state get/set, busy state, equipped weapon type, heading/get-heading-to, motion get/set/clear and portal-state query;
  • inventory/loot/salvage: counts by name/regex/type, give-profile, unopened corpse queries, ustadd, ustopen, ustsalvage;
  • fellowship/quest/XP: thirteen fellowship queries, quest state/progress, seven XP-meter operations;
  • UI/options/network/login: status HUD, view/control get/set/visibility, VT option/meta get/set, macro status, UtilityBelt options, regex capture, network clients and next-login control.

This catalog is a compatibility test ledger. Each name must eventually be implemented, deliberately aliased, or marked unsupported with a documented reason; silent omission is not acceptable.

4. acdream mapping after the 2026-08 campaigns

The 2026-07 report's architecture remains correct, but its gap table is stale. The Runtime now owns inventory transactions, selection, combat mode and power state, casting, fellowship, allegiance, vendor and secure-trade state. MossTank already consumes a small BCL-only IAutomationSurface for vitals, skills, spells, enchantments, casting and local chat.

The gaps relevant to the first autocombat milestone are narrower:

Need Canonical owner today Plugin gap
hostile query and live position RuntimeEntityDirectory + ClientObjectTable no target snapshot/query
health and selected target RuntimeActionState no combat view
melee/missile charge/release RuntimeCombatAttackState no command surface
combat-mode transition RuntimeCombatModeState no command surface
known offensive spells Spellbook only self buffs are enumerated
target-specific cast selection + RuntimeSpellCastState possible only by composing two old services
polished plugin controls retained IUiRegistry markup markup lacks bound visibility/enabled/style affordances

The first implementation therefore does not need a second runtime bridge or a second object model. It needs a narrow additive projection of those exact owners.

5. Decisions for MossTank

  1. MossTank remains an ordinary plugin. It never references App, Runtime, rendering, networking or DAT assemblies.
  2. The host API exposes snapshots and attempt-style commands; MossTank owns target scoring, rule ordering, spell/attack choice and timing.
  3. The first combat milestone supports melee, missile and direct offensive magic, target lock, range/angle/hybrid scoring, priority rules, attack height and power. Navigation, weapon swapping, debuffs, vulnerabilities, pets and monster expressions are later combat slices, not hidden stubs.
  4. Expressions will use UtilityBelt's richer typed semantics. Compatibility is defined by parser/evaluator tests and the audited function ledger, not by copying UtilityBelt implementation code.
  5. Native MossTank profiles will be versioned JSON. Importers for VTank files can be added later without constraining the internal model.
  6. The UI uses acdream's retained plugin UI contract. Missing generic controls should improve that contract/markup rather than making MossTank depend on a presentation implementation.

5.1 Follow-up implementation findings (2026-08-27)

VTank's official e0.d(name) first looks in MonsterDamageOverrides, then maps the monster to SpeciesDamages; ga.g(...) walks that ordered preference list and finally tries the unlisted elements 0..6. acdream already projects retail CreatureType as PluginCombatTarget.SpeciesId, so MossTank can bypass VTank's name-to-species compatibility table while preserving the same ordered damage result. Exact name overrides still win. The imported official feed has 59 overrides and 103 species rows.

5.2 Official inventory and loot findings (2026-08-27)

The official VTank assembly and its GameInfoDB were inspected rather than inferring behavior from the UI labels:

  • el.cs/cf.cs supply 757 exact craft rows; prerequisites are recursive and share the canonical item-use transaction;
  • fo.cs identifies every corpse before selection, parses Killed by ..., admits the player's own corpse immediately, admits a Share Loot fellow immediately, waits 100 seconds for a non-sharing fellow or unrelated public corpse, and never crosses ownership on another player's rare-generating corpse;
  • the default corpse-open retry contract is 30 attempts, then a 200-second blacklist; completed corpse records expire after 60 minutes;
  • hv.cs applies the ordered loot rule first, then falls back to readable unknown scrolls and automatic mana-stone/tank acquisition;
  • dy.cs proves that ManaTank is a mana-bearing donor target, not a worn-item recharge consumable. A ManaStone is used on that donor when its mana is at least ManaTankMinimumMana (default 1000);
  • c7.cs combines only same-material salvage bags in exact workmanship bands <7, 7<9, 9<10, and exactly 10; one bugged source is abandoned after 40 failed combine attempts;
  • gmSalvageUI::Salvage calls CM_Inventory::Event_CreateTinkeringTool: game action 0x027D, tool id, then PackableList<unsigned long> (count plus ordered item ids). This same operation handles ordinary source salvage and salvage-bag combination.

The native implementation keeps settings, loot, route, and meta documents independent, matching VTank's profile model while using versioned JSON as the working format. It also emits and imports exact compatibility files: uTank2 NAV 1.2, CondAct .met, and VTClassic UTL 1 (plus legacy UTL v0 reads). The UTL port preserves unknown length-delimited requirement and extra-block payloads, executes the complete 31-type requirement vocabulary, and carries the SalvageCombine material ranges/value modes into the live combine planner. VTClassic's color rules use the original ordered ObjDesc subpalettes and the original sample index length*16 + offset*32 + 8, resolved from portal DAT palette colors rather than approximated from icon pixels.

Profiles are implemented over manifest-scoped JSON with exact VTank files as an interchange/export layer: By char hashes the canonical character name into a distinct document, named profiles are explicit shared snapshots, and the index records owner plus per-character active selection. Create/copy/clear/select all hot- load the same mutable policy owners already borrowed by the controllers. The generic retained markup contract gained editable fields and retail dropdown menus for this editor; later Monsters, Loot, Route, and Meta editors reuse the same controls.

6. Acceptance boundary for “autocombat ported”

The milestone is complete when an in-world MossTank panel can enable/disable combat, periodically capture canonical hostile targets, preserve a valid locked target, choose a target by configured range/angle/hybrid policy and priority, enter the equipped default combat mode, drive retail's physical press/charge/release state machine at configured height/power, or cast the best usable learned direct offensive spell in magic mode. It must stop cleanly on session loss, invalid/dead/out-of-range targets, and user disable; it must not duplicate Runtime state or issue overlapping requests.

Full VTank parity is the campaign target. This acceptance boundary is only the first executable slice requested for this work session.

7. Official binary combat-item findings (2026-08-27)

The official vt.tar.gz update was decompiled for behavior research and the live GameInfoDB v9 feed was read directly. The decisive implementations are dz.cs (debuff source selection), ga.cs (item classification), gs.cs (caster-item confirmation), bo.cs (physical/proc confirmation), and hi.cs (attack-power policy).

  • dz.b.CompareTo ranks spell quality then source skill/spellcraft for SpellLevel, reverses those two for Skill, and gives a learned spell the final tie. Spell quality is normally spell difficulty.
  • Caster items activate on the target. Melee/missile proc weapons are equipped and repeatedly attack at power 0/1 respectively. Neither path counts as applied until color-7 combat chat matches ^You cast (.*) on .*$.
  • Grenades are missile-class items with CombatUse 0 and Phial in the name; the official database contains exactly 72 names across eight material tiers, with Alchemy requirements 75..400 and spellcraft 100..520.
  • Normal physical attack power is not a smooth heuristic. hi.cs emits the exact 0, .2, .49, .5 or 1 values for slash/pierce hybrid arrangements, then clamps to .11..90 when trained Recklessness is enabled.

These findings require three host facts VTank formerly obtained through Decal: retained per-item appraisal SpellBooks, ordered transcript capture, and an explicit combat-mode command. They are additive BCL plugin contracts; MossTank retains all source-choice and retry policy.