acdream/docs/research/2026-07-29-vtank-plugin-automation-requirements.md
Erik 6077ce4d23 docs: VTank requirements research - the plugin-automation milestone model
User-requested (2026-07-29): the plugin API must eventually support
VTank-class automation state machines written as acdream plugins. The
research decodes the full Virindi Tank surface from wiki archives and
primary source (the meta FSM''s complete condition/action vocabulary and
.met encodings, the expression language''s 67-function catalog, all ten
nav-point types with .nav wire payloads, VTClassic''s loot-rule type ids
and .utl format), derives the implied host API surface, and grounds it
against acdream: the K2 headless-bot triad is already the right
substrate, the VTank-like engine itself belongs in plugin-land, and the
milestone is a 5-step bridge/query/enchantment/transaction/nav sequence
where steps 2-4 ride on landed M3/M4 work. Filed in the post-Vulkan
intake as a C-bucket milestone candidate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 16:29:49 +02:00

21 KiB
Raw Blame History

Virindi Tank as the requirements model for acdream's plugin-automation milestone

User-requested research (2026-07-29): the plugin API's north star is that a user can write automation state machines as acdream plugins with VTank-class power (the famous Decal automation plugin for original AC — http://virindi.net/wiki/index.php/Virindi_Tank). The live wiki is effectively unreachable (self-signed TLS; the HTTP wiki returns a database error); this report was built from Wayback Machine snapshots of the wiki pages plus primary source code (metaf's .met/.nav encodings, VTClassic's loot-rule classes) — full citation list at the end.

The architectural conclusion up front: VTank's meta state machine, expression language, and loot-rule engine are PLUGIN-land, not host-land — VTank built them on Decal's primitives. acdream's milestone ships the primitive layer; a VTank-compatible meta/nav/loot engine can then be an acdream plugin — even file-compatible with .met/.nav/.utl, all of whose encodings are decoded below. And the K2 headless-bot triad (Tick(IGameRuntimeView, IGameRuntimeCommands) + IRuntimeEventObserver) is already exactly the right substrate: synchronous borrowed reads, generation-gated typed commands with attempt semantics, an ordered delta stream. The milestone is largely a bridging + query-surface exercise, not a new architecture.


1. Capability inventory

1.1 Combat macro

  • Attack styles: melee, missile, mage, full hybrid templates; two-handed; Void; Summoning; harm/martyr mage mode; alchemical grenades; lenses; cast-on-strike debuffing weapons; war/void streak spells. VTank auto-selects the best attack type from the configured weapon list (Items tab: wands/melee/missile/shields/pets, per-monster weapon+offhand overrides).
  • Target selection: per-monster priority -1 (ignore) through 04; TargetSelectMethod = by range / by angle / both (angle when near, range beyond TargetSelectAngleRange); TargetLock; blacklisting of unhittable targets (BlacklistMonsterAttemptCount/TimeoutSeconds); ghost-monster detection two ways (N failed cast starts, or no HP updates for N seconds) with a forced client-side delete.
  • Monster list = a rule table, first-match-wins after DEFAULT, keyed by name or by monster expressions over: true, false, name, typeid, species, maxhp, range (dynamic), hasshield (dynamic), setting_<OptionName> (dynamic), metastate (dynamic) with a full operator set (( ) % / * + - # != == > < >= <= && || and \ escape; # is regex-match). Per-entry action matrix: Fester, Broadside, Gravity Well, Imperil, Yield, Vuln, Attack, Ring, Streak, void curses (WC/FC/Cp/DC/Cs), damage type (or <Auto>), extra vuln element, weapon/offhand/pet damage type per monster.
  • Debuff sequencing: DebuffEachFirst = one / same-priority-group / all-before-attacking; DebuffSelectionMethod = by spell level or by (buffed) skill; DebuffPrecastSeconds re-application window; SwitchWandToDebuff.
  • AoE logic: ring when ≥ MinimumRingTargets within RingDistance; arcs over bolts beyond ArcRange.
  • Melee specifics: attack height (high/mid/low), auto attack-power by weapon damage type, power-bar slider (/vt setattackbar 0..1), recklessness, multi-step weapon-swap state machine with peace-mode retry counting (/vt equipitemsfor <monster> executes one step per call).
  • Pets: summon when ≥ PetMonsterDensity targets within pet range; per-monster pet element; idle/combat refill thresholds; /vt testpet.

1.2 Buffing engine

  • Auto-buffs every trained attribute/skill plus protections and banes — derived from the character's skills, spellbook, and available components (the Buffs tab only adds untrained-skill buffs or exclusions). Prot/bane sets All/None/B/BPS/BPSA/ALFC/Custom via BuffProfile_Prots/_Banes.
  • Rebuff below 5 minutes remaining (RebuffTimeRemainingSeconds); idle early-rebuff (IdleBuffTopoff); force-buff/cancel; level 8s; item-buff durations persisted across logins; buffing with consumables (items under 5-minute duration rejected).
  • Skill gating with signed thresholds SpellDiffExcessThreshold-Buff/-Hunt (negative = attempt above-skill spells) — exposed to expressions as getcancastspell_buff/hunt (scarab count + buffed skill + spellbook).

1.3 Vitals / heal / restam management

  • Three recharge tiers, each per-vital: in-combat trigger percentages (Recharge-Norm-*), idle top-off (Recharge-NoTarg-*), and fellowship helper (Recharge-Helper-* + per-vital max distances; requires fellow panel open or a VI2 fellowship).
  • Healing kits (minimum success chance, optional drop-to-peace first), stamina→health and mana→health spell multipliers, post-switch recharge boost, Healer's Heart support.
  • Dispel-on-vuln: detects a level-7 vuln landing and responds with dispel gems/potions and/or Eradicate self.

1.4 Inventory upkeep and crafting

AutoStack (stack toward bottom-most so the partial stack burns first), AutoCram (main pack → side packs), pea splitting with three priority tiers, auto-crafting consumables (kits, foods, arrowheads, special ammo), mana-stone economy (loot stones to a count, fill from loot above a mana floor, drain onto worn items at a refill percentage, even while the macro is off), lockpicks for auto-picking doors with a skill-vs-difficulty threshold.

1.5 Looting — VTClassic and the loot-plugin seam

VTank's looting is plugin-based: the host macro does corpse approach/open/wait; a loot plugin classifies each item (VTClassic canonical; Alinco3/GearFoundry use the same uTank2 LootPlugins seam). Macro-side knobs: approach ranges, open retry + blacklist, CorpseItemAppearanceTimeoutSeconds / CorpseItemIDTimeoutSeconds (wait for items, then for IDs), loot all/fellow/rares-only, priority boost, read-unknown-scrolls, salvage combining per-profile workmanship ranges.

From VTClassic source: profile = ordered rules; rule = name + action + AND-ed requirements; first match classifies, evaluated against the item's retail appraisal property bags (LongValueKeys, DoubleValueKeys, StringValueKeys, spell list — what /vt propertydump prints).

  • Actions (eLootAction): NoLoot=0, Keep=1, Salvage=2, Sell=3, Read=4, User1User5=59, KeepUpTo=10.
  • Requirement types (eLootRuleType): SpellNameMatch(0), StringValueMatch(1), LongValKeyLE(2), LongValKeyGE(3), DoubleValKeyLE(4), DoubleValKeyGE(5), DamagePercentGE(6), ObjectClass(7), SpellCountGE(8), SpellMatch(9), MinDamageGE(10), LongValKeyFlagExists(11), LongValKeyE(12), LongValKeyNE(13); armor color: AnySimilarColor(14), SimilarColorArmorType(15), SlotSimilarColor(16), SlotExactPalette(17); character-state: CharacterSkillGE(1000), CharacterMainPackEmptySlotsGE(1001), CharacterLevelGE(1002), CharacterLevelLE(1003), CharacterBaseSkill(1004); computed buffed-value: BuffedMedianDamageGE(2000), BuffedMissileDamageGE(2001), BuffedLongValKeyGE(2003), BuffedDoubleValKeyGE(2005), CalcdBuffedTinkedDamageGE(2006), TotalRatingsGE(2007), CalcedBuffedTinkedTargetMeleeGE(2008).
  • The buffed/calced family means the loot engine needs the character's current buffed skills and tinkering math, not just item properties; color rules need item palette data.
  • .utl file: text — UTL header, version, rule count, rule blocks, extra blocks (salvage-combine workmanship ranges like 1-5,6-7,8,9,10).

1.6 Navigation

Route types (NavTypeID): Circular=1 (ABC-ABC, enter at closest point), Linear=2 (ABC-CBA ping-pong), Follow=3 (chase a named player; FollowAroundCorners = replay leader's path vs run straight), Once=4 (points consumed as reached; runback routes).

Nav point vocabulary (NTypeID — the exact .nav wire order):

Type ID Payload
Point 0 x, y, z
Portal (deprecated) 1 x, y, z, portal GUID
Recall 2 x, y, z, exact recall-spell name (fixed ~30-entry list)
Pause 3 x, y, z, milliseconds
Chat 4 x, y, z, arbitrary chat/slash text
Open Vendor 5 x, y, z, vendor GUID, name
Use Portal/NPC 6 own x,y,z + target x,y,z + target ObjectClass + name
NPC Talk 7 same as 6 — use repeatedly until a tell is received
Checkpoint 8 x, y, z — wait for SERVER-confirmed position
Jump 9 x, y, z, heading, shift bool, charge ms (+strafe variant)

.nav file: plain text, uTank2 NAV 1.2 header, route-type int, node count, node records (verified against a real route file). Runtime knobs: arrival radius (NavCloseStopRange), off-course abort (NavFarStopRange), nav-before-combat priority boost, door opening en route, reverse-route. Routes embed inside meta files (metaf EmbedNav, optional transform).

1.7 Fellowship behaviors

Auto-recruit on tell, waiting-list management, leader queries, AFK-leader replacement votes — all via chat tells. Plus helper healing (1.3), fellow-corpse looting, and (via Virindi Integrator 2) out-of-band fellow coordination (prefer already-vulned targets, avoid double-vulning).

1.8 The Meta system — the state machine

Named states (start = "Default"); rule = (state, condition, action); rules checked only in their state; each rule fires once per state entry (re-armed by re-entry). Conditions evaluated ~every 293 ms plus whenever the macro decides what to do next. Call stack: Call Meta State pushes a return state, Return From Call pops (empty stack = error + meta disabled). Watchdog: per-state "if not moved ≥ distance in N seconds, call state S."

Conditions (CTypeID 028): Never(0), Always(1), All(2), Any(3) (nesting combinators), ChatMatch(4) regex, MainSlotsLE(5), SecsInStateGE(6), NavEmpty(7), Death(8), VendorOpen(9), VendorClosed(10), ItemCountLE(11)/GE(12), MobsInDist_Name(13) (count, distance, name-regex), MobsInDist_Priority(14), NeedToBuff(15), NoMobsInDist(16), BlockE(17), CellE(18), IntoPortal(19)/ ExitPortal(20), Not(21), PSecsInStateGE(22) (persistent timer), SecsOnSpellGE(23) (enchantment time remaining), BuPercentGE(24) burden, DistToRteGE(25), Expr(26), ChatCapture(28) — regex with named capture groups saved to variables (capturegroup_<name>, capturecolor) plus chat-color filtering.

Actions (ATypeID 015): None(0), SetState(1), Chat(2) (the universal escape hatch — /vt commands, tells, other plugins), DoAll(3), EmbedNav(4), CallState(5), Return(6), DoExpr(7), ChatExpr(8), SetWatchdog(9), ClearWatchdog(10), GetOpt(11)/SetOpt(12) (read/write ANY host option — the macro reconfigures itself), CreateView(13), DestroyView(14), DestroyAllViews(15)Meta Views: XML-defined control panels (controls with actionexpr/setstate attributes) so a meta ships its own UI.

1.9 Meta Expressions — the scripting language

Numbers (doubles; booleans 0/1), strings, objects (coordinates, world objects, stopwatches, view controls). Operators by precedence: \ ( ) % / * + - #(regex) != == > < >= <= && || ^ ;. Variables persist to relog (setvar/getvar/testvar/touchvar/clearvar/clearallvars). 67 functions documented in the 2016 snapshot (UtilityBelt's compatible dialect grew it to ~200):

  • Raw property reads by retail key id: getcharintprop, getchardoubleprop, getcharquadprop, getcharboolprop, getcharstringprop (wiki ships key tables: Level=25, Deaths=43, augmentations 218328, luminance auras 333+, TotalExperience quad 1, FellowshipName string 10, …).
  • Vitals/skills: getcharvital_base/current/buffedmax(1|2|3), getcharskill_traininglevel/base/buffed(skillId).
  • Spells: getisspellknown, getcancastspell_hunt, getcancastspell_buff.
  • Location: getplayerlandcell, getplayercoordinates (from the PHYSICS prediction model), coordinate objects (coordinategetns/getwe/getz/tostring/parse, coordinatedistancewithz/distanceflat).
  • World-object queries: wobjectgetphysicscoordinates, wobjectgetname, wobjectgetobjectclass, wobjectgettemplatetype, wobjectgetisdooropen, wobjectfindnearestmonster, wobjectfindnearestdoor, wobjectfindnearestbyobjectclass, wobjectfindnearestbynameandobjectclass, wobjectfindininventorybytemplatetype/byname/bynamerx, wobjectgetselection, wobjectgetplayer — ObjectClass enum 043 (Monster=5, Portal=14, Corpse=27, Npc=37, Salvage=39, Ust=40, Scroll=42, CombatPet=43).
  • Action functions (async attempt semantics — 0 not-yet / 1 begun / 2 impossible): actiontryselect, actiontryuseitem, actiontryapplyitem, actiontrygiveitem, actiontryequipanywand, actiontrycastbyid, actiontrycastbyidontarget.
  • Chat/UI: chatbox, chatboxpaste, statushud, statushudcolored, uigetcontrol, uisetlabel.
  • Utility: iif, istrue/isfalse, randint, cstr/cstrf/cnumber, strlen, floor/ceiling/round/abs, stopwatch*, getobjectinternaltype.
  • Testing: /vt mexec <expr>, /vt listmetafunctions, /vt metafunchelp.

1.10 Commands, profiles, files

/vt surface: per-subsystem profile save/load (settings|nav|loot|meta, per-character variants), opt list/get/set/setinall (every internal variable scriptable), macro start/stop, forcebuff, setmetastate, fakedeath (test hook), testitem (dry-run loot classification), propertydump, testmonster, testspell, getdb (spell/species/material tables), nav editing (addnavpt/addnavjump/addnavcheckpoint/reverseroute), jump/tapjump/ setattackbar, dump commands, typed debug logging channels. Persistence: profile directory with .settings (global + [Char]), .nav, .utl, .met; every UI change auto-saves; all four profile kinds hot-loadable from chat/meta (how metas compose: /vt nav load runback).

1.11 The companion-plugin bundle

VTClassic looter + out-of-game rule editor; Virindi Integrator 2 (account-based cross-world groups/tells, fellow coordination); Reporter; Chat System 5 (rule-based chat filter/recolor/sounds); Item Tool (bulk give, buy/sell, mule transfer, comp purchasing — other plugins call into it); Follower; HUDs (status HUD writable from expressions, VTank remote, vitals bars); Hotkey System; Sense (spawn alerts); Window Tool; Global Inventory (cross-character item index); GoArrow; SSSort. Ecosystem: metaf (text ↔ .met/.nav compiler), UtilityBelt (modern superset). Multi-account orchestration is built on metas + chat tells — no special API.


2. The implied plugin-API surface

Game-state reads: (1) world-object table with full property bags — id, name, ObjectClass-style classification, weenie/template type, PHYSICS position, heading/distance, container/wielder links, door state, vendor identity; query verbs (nearest-by-class, name-regex+class, in-range counts, inventory filters). (2) appraisal surface — per-object retail property bags (int/bool/double/string/quad + spell list + palette + material/workmanship), ID-request command + ID-arrived event. (3) character sheet — vitals (base/current/buffed max), skills (training/base/buffed), burden, pack slots, level/XP/luminance, augmentations, spellbook, component counts. (4) enchantment table with remaining seconds per spell id (+item-buff timers persisted). (5) spatial identity — landblock/landcell, portalspace flag, coordinate conversions. (6) target combat state — per-monster HP stream, equipment reads. (7) fellowship state. (8) vendor/trade state.

Commands (attempt semantics + busy arbitration): select, examine/ID, use object, use X on Y, give to NPC/player, loot specific item from container, salvage, read scroll, equip weapon-set (multi-step), combat mode + height/power, attack, cast (self/on-target) with pre-flight checks, move-to / follow, jump(heading, shift, charge, strafe), vendor open + buy/sell, split/stack/move items, lockpick, chat injection, and runtime option get/set on the automation host itself.

Events: chat (channel/color, capture-friendly), death, vendor open/close, portalspace enter/exit, cast begun/landed/fizzled (and never-started), item appeared on corpse, ID arrived, target HP update, server-confirmed position (checkpoint semantics), nav arrival, fellowship changes.

Scheduling & persistence: ~3 Hz decision tick interleaved before the host's own arbitration; subsystem priority (heal > combat > nav > loot with boosts); per-subsystem hot-loadable profiles (global + per-character); a persisted per-character key/value store.

UI: declarative runtime panels (markup), button → action/state bindings, a writable status-HUD channel.


3. Mapping to acdream today

Already-strong seams: the K2 headless triad (IHeadlessBotPolicy.Tick (view, commands) + IRuntimeEventObserver) is precisely the loop a VTank-class engine needs — synchronous borrowed reads, generation-gated typed commands (RuntimeCommandResult ≈ VTank's attempt semantics), ordered delta stream (VTank's 293 ms meta tick maps onto Tick). Event coverage already spans lifecycle, command receipts, entity, inventory, chat, movement, portal deltas (IntoPortal/ExitPortal fall out for free), combat. Command coverage: Selection/Combat/Magic/Movement/Chat/Portal/Spellbook/Character/ Social. Character reads: vitals/attributes/skills incl. FormulaBonus (≈ buffed), KnowsSpell; the enchantment registry exists Runtime-side (M3). IUiRegistry.AddMarkupPanel is already a better-typed Meta Views.

The gap: AcDream.Plugin.Abstractions today exposes only IPluginHost { Log, State (4-field entity snapshots), Events (EntitySpawned), Selection, Ui }. Everything VTank-class lives Runtime-side but is not plugin-visible:

Cluster Status
Bot loop (tick+commands+ordered events) Exists (K2); not bridged to plugins
Entity queries w/ classification/name/distance Deltas exist; snapshots lack name/class/type; no query verbs
Property bags / appraisal M4 Slices 13 landed assessment client-side; no plugin surface
Enchantment table w/ remaining time State exists (M3); no enumerating view
Cast-on-target with outcome events Command exists; outcomes implicit only
Use/apply/give/loot-item/salvage/vendor Use+PickUp exist; vendor = M4 Slices 56 in flight; apply/give absent
Move-to / follow / jump / routes Absent — movement is WASD-intent-shaped; no point-goal primitive
Fellowship state Not in Runtime views yet
Plugin persistence/options Untyped (plugins can own files; low urgency)

4. Milestone shape — dependency-ordered API additions

  1. Plugin↔Runtime bridge (the enabler): mirror the K2 triad into BCL-only Plugin.Abstractions — per-plugin Tick(view, commands), generation-gated command groups, ordered event observer. Upgrades plugins to headless-bot parity; chat in/out unlocks the VTank composition idiom.
  2. Entity/property query surface: enrich plugin entity snapshots (name/class/weenie type/physics position); query verbs; per-object property bags in the retail key space; ID-request command + ID-arrived event. Rides on the landed M4 assessment work.
  3. Spell/enchantment surface: enumerable active enchantments (spell id, layer, seconds remaining), can-cast prediction (components + skill vs difficulty — the getcancastspell pair), explicit cast-on-target, cast-outcome events.
  4. Interaction/transaction commands: use-by-id, apply-on, give, container take-specific, salvage, vendor buy/sell — over the existing one-transaction gate, with receipts as events. After M4 Slices 46.
  5. Nav/move-to layer: point-goal movement with arrival/stuck/off-course events + a server-confirmed-arrival variant (checkpoint semantics; position-ack tracking exists), follow-entity, jump. The route types and nav-point vocabulary are then pure plugin data; optional .nav import.

(1) gates everything; (2)/(3) independent after (1); (4) depends on M4's in-flight slices; (5) last — route automation needs combat/loot reads to decide when to move.


Sources

Wiki content via Wayback Machine snapshots (live site: self-signed TLS / database error): Virindi_Tank (rev. 2024-01-12), Virindi_Tank_Meta_System, Meta_Expressions, Meta_ChatMessageCapture_Condition, Meta_Views, Virindi_Tank_Commands, Virindi_Tank_Standard_Options, Virindi_Tank_Advanced_Options, Virindi_Tank_Monster_List_Matching, Virindi_Tank_Fellowship_Commands, BeginnerBundleGuide, VTClassic_Tutorial, Virindi_Tank_Changelog — all under http://www.virindi.net/wiki/index.php/….

Code/format ground truth: github.com/JJEII/metaf (CTypeID/ATypeID/ NavTypeID/NTypeID encodings + metafReference.af payload tables); github.com/leo-duran/virindi-public-clone (VTClassic LootRules.cs); github.com/aquafir/ACE.BaseMod (vendored eLootAction); github.com/lino-ranta/vtank-routes + vtank-loot-profiles (on-disk .nav / .utl verification); utilitybelt.gitlab.io/docs (modern ecosystem).

acdream grounding: src/AcDream.Plugin.Abstractions/*, src/AcDream.Runtime/GameRuntime{Commands,Events,Views,GameplayViews}.cs, src/AcDream.Headless/Policies/HeadlessBotPolicy.cs.