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

384 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`.