feat(mosstank): add VTank-style automation PoC

This commit is contained in:
Erik 2026-08-27 18:57:21 +02:00
parent f6fe0f2a4f
commit 4e6e9bc9d9
212 changed files with 49462 additions and 416 deletions

View file

@ -24,6 +24,79 @@ What does NOT go here:
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
## #451 — GLFW can dereference another acdream process's private window pointer after cross-process activation
**Status:** IN-PROGRESS — exact root fixed; 100-switch/30-minute dual-client
stress passed 2026-08-27. Graceful exit of both sessions remains before closure.
**Component:** graphical host / GLFW Win32 event pump / multi-process stability.
**Severity:** HIGH for multi-account play; one of the two sessions is lost without
an orderly disconnect.
Running two copies of the exact isolated `app-release23` graphical artifact
against local ACE reproduced the same access violation three times. The
faulting process varied: secondary PID 13688 at 15:01:39, primary PID 15300 at
15:03:35, and fresh primary PID 31412 at 15:08:58. The last occurrence fired
while the fresh primary was still on character selection, before EnterWorld;
the already-in-world secondary survived. Windows Application Error reports
all three as `coreclr.dll` exception `0xC0000005`, fault offset `0x356d4f`.
The managed terminal stack is only:
```text
Silk.NET.Windowing.WindowExtensions...Run
Silk.NET.Windowing.Internals.ViewImplementationBase.Run
Silk.NET.Windowing.Glfw.GlfwWindow.Run
AcDream.App.Rendering.GameWindow.Run
```
This is not #422's rare `0xC0000374` heap corruption during graceful process
exit: #451 happens while both graphical clients are active and reproduces
quickly. It is also not a MossTank/plugin-API, CoreCLR, Vulkan, PAK, or world-
cache failure.
**Exact root (first-chance cdb proof):** the access violation is in packaged
GLFW's Win32 event pump at `glfw3+0x10681`, not in CoreCLR. During its modifier-
key repair pass `_glfwPollEventsWin32` calls `GetActiveWindow`, then
`GetPropW(hwnd, L"GLFW")`, and dereferences the returned value as this
process's `_GLFWwindow*`. Windows UI automation temporarily joins input queues,
so the primary process can receive the secondary process's HWND. Because every
GLFW process uses the same `GLFW` property name, `GetPropW` succeeds but returns
the secondary process's private pointer. The crashed primary had
`rbx=00000202a7180ab0`; a debugger breakpoint in the surviving secondary
reported its own valid `ACTIVE_GLFW_WINDOW=00000202a7180ab0` — exact pointer
identity across the process boundary.
**Fix:** `Win32GlfwActiveWindowGuard` patches only `glfw3.dll`'s import-address-
table slot for `USER32!GetActiveWindow`, after the GLFW library is loaded and
before `glfwInit`/window creation. The replacement returns the real HWND only
when `GetWindowThreadProcessId` says it belongs to the current process;
otherwise it returns zero, GLFW's existing safe "nothing to repair" branch.
There is no system-wide hook and no other module is changed. Four focused
tests cover local, foreign, null and unowned HWNDs.
The isolated `app-release24` live gate launched two graphical clients, both
logged `GLFW foreign-active-window guard installed (#451)`, entered the world,
and remained responsive through 100 rapid forced cross-process activation
switches — the exact prior trigger — plus a 30-minute combined in-world soak.
The local peer API then passed in both directions: the secondary evaluated the
primary heartbeat and returned `+Acdream`. A two-member fellowship gate also
passed with both canonical rosters populated and the secondary returning `2`
from `getfellowshipcount[]`; both processes remained alive and responsive.
Evidence:
- `artifacts/live-gates/mosstank-final23-secondary/`
- `artifacts/live-gates/mosstank-final23-secondary2/`
- `artifacts/live-gates/mosstank-final23-primary5/`
- `artifacts/live-gates/i451-cdb-primary-attach/cdb.log`
- `artifacts/live-gates/i451-cdb-secondary/cdb.log`
- `artifacts/live-gates/i451-guard-primary2/`
- `artifacts/live-gates/i451-guard-secondary/`
- Windows Application Error events at 2026-08-27 15:01:39, 15:03:35 and
15:08:58 (same module, exception and offset).
**Next:** close only after both `app-release24` sessions exit gracefully; the
activation and sustained in-world portions of the regression gate have passed.
## #450 — Fast character re-entry after logout can remain in portal space at `lb 0/0`
**Status:** DONE — OWNER-ACCEPTED 2026-08-26 in the combined client-parity gate.

View file

@ -137,6 +137,68 @@ loads none). The headless adapter projects entity snapshots on demand from the
canonical Runtime view, subscribes to Runtime's ordered events, and borrows the
exact Runtime selection owner; it does not mirror gameplay state.
Graphical plugin panels are first-class retained windows. A plugin calls
`IUiRegistry.AddPanel` with a BCL-only `PluginPanelDescriptor`; Core's scoped
host authenticates the owner from the loaded manifest, and App derives the
stable identity `plugin:{pluginId}:{windowId}`. The host, not the plugin, owns
window geometry, z-order, persisted visibility, minimize/restore chrome, and
the shared right-edge plugin shelf. Minimizing only hides the presentation:
the plugin session, event subscriptions, automation policy, and binding object
remain live. Legacy `AddMarkupPanel` registrations are enriched into the same
first-class path, so API-v1 plugins keep working without a second lifecycle.
The markup vocabulary includes nested groups plus retained tab, toggle,
slider, editable-field, and retail-menu controls. Fields bind live
`Action<string>` change/submit callbacks and menus bind an
`IEnumerable<string>` plus selection callback, so plugin-owned profile/rule
editors stay behind the BCL contract instead of importing App widgets. These
are presentation bindings only and never become parallel gameplay owners.
Durable plugin data uses the BCL-only `IPluginHost.Storage` contract. Core's
manifest-authenticated scoped host prefixes every logical key with the loaded
plugin id; graphical composition writes atomically beneath the per-user config
root (`plugins/{pluginId}/...`). Plugins receive neither another plugin's
namespace nor a machine-specific path. Hosts without durable storage expose
`NoOpPluginStorage` and report the capability unavailable.
The additive `List(prefix)` operation enumerates only keys inside that same
authenticated namespace, allowing plugins to discover explicit import/export
files without receiving a filesystem path or crossing plugin ownership.
`IPluginHost.Automation` is the additive gameplay-automation projection. Its
character, spell, magic, chat, combat, equipment, item, loot, fellowship,
enchantment-observation, and navigation
groups contain BCL-only immutable snapshots plus attempt-style commands; the
graphical implementation borrows the exact `GameRuntime`
character/action/entity/object/vendor/fellowship owners. Item projections also
carry Virindi's stable ObjectClass plus ordered ObjDesc subpalette samples;
the graphical host resolves each representative RGB directly from portal DAT
using VTank's sample-index formula. MossTank owns all
macro policy (buff planning, target rules, selection scoring, corpse policy,
loot-rule ordering and action timing). In particular, `ICombatAutomation`
does not create a plugin combat model: each hostile capture is a detached
point-in-time projection of `RuntimeHostileTargetQuery`, and physical commands
enter the canonical `RuntimeCombatModeState` / `RuntimeCombatAttackState`
press-charge-release state machine. Item and loot commands similarly enter
App's one `ItemInteractionController`: appraisal, use/apply, pickup,
move/split/merge/drop/give, retail 0x027D salvage, and current-vendor sale
reuse the same readiness checks, reservations, wire sends, and authoritative
completion/object-table signals as retained retail UI. Plugins never hold an
optimistic inventory or vendor shadow. Navigation similarly projects live and
server-accepted position, portal/object state, and semantic movement levels;
the App host applies those levels through Runtime's one command interpreter.
Route sequencing, steering cones, follow breadcrumbs, checkpoint policy,
door/lockpick decisions and portal retry behavior remain plugin-owned. The
shared enchantment-observation group is deliberately a confirmed-cast timer
ledger rather than another authoritative spellbook: the host records successful
local duration casts and cooperating plugins can report their own confirmed
casts, matching VTank's `LogSpellCast` contract. It resets at session detach;
dispel/debuff policy remains plugin-owned. The
inert default remains
`NoOpAutomationSurface`, preserving one plugin code path on hosts without a
live gameplay session.
`ICharacterInfo.Name` projects the canonical local `ClientObject.Name` (empty
when unavailable) solely for per-character plugin profile scoping; it does not
introduce a second identity owner.
Core `SelectionState` is the sole selected-object owner for world,
radar, inventory, paperdoll, toolbar, use/examine consumers, and plugins;
`IPluginHost.Selection` exposes that same state and retail-style old/new callback.
@ -420,6 +482,10 @@ src/
IGameState.cs -> done
IEvents.cs -> done
ISelectionService.cs -> done
IPluginStorage.cs -> manifest-scoped durable text profiles
Automation.cs -> character/spell/magic/chat automation groups
CombatAutomation.cs -> hostile snapshots + retail combat attempts
EnchantmentAutomation.cs -> shared confirmed duration-cast timer ledger
AcDream.App/ Layer 1 + Layer 4 wiring
Platform/

View file

@ -98,6 +98,7 @@ dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Release
| `ACDREAM_NEAR_RADIUS` | `=<int>` | Overrides preset's `NearRadius` (inner streaming ring radius) | Changes the streaming/render window size — CLAUDE.md: leave unset for measurement/gate runs | preset's `NearRadius` (Low=2, Medium=3, High=4, Ultra=5) | `QualitySettings.WithEnvOverrides` (`QualityPreset.cs:46`) |
| `ACDREAM_NO_AUDIO` | `=1` | Suppresses OpenAL device init entirely (headless / driver-broken machines). | Fail-open design: even without this flag, a missing/broken OpenAL driver already makes `IsAvailable=false` and all `Play*` calls no-ops — this flag is the explicit, deliberate version of the same fallback. When set, `LiveSessionWorldRuntime.WorldAudio` is `null` and logout-audio reset/resume steps no-op. | `false` | `RuntimeOptions.NoAudio``GameWindow.cs:1430``ContentEffectsAudioCompositionPhase``OpenAlAudioEngine.cs` (fail-open doc), `LiveSessionRuntimeFactory.cs:71` (`WorldAudio` gate) |
| `ACDREAM_PAK_PATH` | `=<path>` | Overrides the path to the prepared machine-local asset bake (`acdream.pak`) that production world-mesh streaming reads through `IPreparedAssetSource`. | none beyond the obvious | unset → `<datDir>/acdream.pak` | `RuntimeOptions.PreparedAssetPath``ContentEffectsAudioComposition.cs:53,372,379`, `GameWindow.cs:1420` |
| `ACDREAM_PLUGIN_TAGS` | comma-separated tags (maximum 128 tags, 128 characters each) | Advertises machine-local role/group tags through the plugin peer-discovery API, for UtilityBelt-compatible expressions such as client selection by tag. Values are trimmed and deduplicated case-insensitively. | Writes the tags into the bounded local peer heartbeat document while a character is in world; no network traffic leaves the machine. | unset → no tags | `RuntimeOptions.PluginTags``AppAutomationSurface` / `LocalPluginPeerRegistry` |
| `ACDREAM_RESIDENCY_ALPHA_SCRATCH_MIB` | `=<int MiB>` (`>0`, else default; overflow-checked) | Byte ceiling for the retail alpha (translucency) draw queue's scratch buffer | Shrinking below production working set changes translucency-queue eviction/reflow behavior — not comparable to a default-budget perf run | 16 MiB | `ResidencyBudgetOptions.Parse` (`ResidencyBudgetOptions.cs:88-89`), flows through `RuntimeOptions.ResidencyBudgets``AlphaScratchBudgetProfile.Create``RetailAlphaQueue` ctor (`GameWindow.cs:721-725`) |
| `ACDREAM_RESIDENCY_ANIMATION_ENTRIES` | `=<int>` (`>0`, else default) | Entry-count ceiling for the retained animation-data cache | Changes cache eviction cadence for animation data — a perf/memory measurement under a non-default value is not representative | 512 | `ResidencyBudgetOptions.Parse` (`:82-84`) |
| `ACDREAM_RESIDENCY_ANIMATION_MIB` | `=<int MiB>` (`>0`, else default) | Byte ceiling for the retained animation-data cache | Same cache-behavior-change caveat as the entries variant | 64 MiB | `ResidencyBudgetOptions.Parse` (`:79-81`) |

View file

@ -184,17 +184,28 @@ panel through `IPanelRenderer`.
## Plugin UI API
The shipped plugin-facing gameplay UI contract is
`AcDream.Plugin.Abstractions.IUiRegistry.AddMarkupPanel`: a plugin provides
KSML-style markup and a binding object; the host builds it into the retained
`UiRoot` tree. `IPanel`/`IPanelRenderer` remains a first-party developer-panel
contract and is intentionally not referenced by `Plugin.Abstractions`.
The shipped plugin-facing gameplay UI contract is the additive BCL-only
`AcDream.Plugin.Abstractions.IUiRegistry.AddPanel`: a plugin provides a stable
window id/title/icon descriptor, KSML-style markup, and a binding object; the
host builds it into the retained `UiRoot` tree. The API-v1
`AddMarkupPanel` member remains source/binary compatible and is enriched into
the same first-class window route by the scoped host. `IPanel`/
`IPanelRenderer` remains a historical first-party developer-panel contract and
is intentionally not referenced by `Plugin.Abstractions`.
This makes plugin gameplay panels independent of ImGui while allowing them to
share the retained input, window, and DAT-sprite runtime. Registrations made
before the GL host exists are buffered. In builds where retail UI is disabled,
they remain registered but have no gameplay surface; the long-term release
configuration enables retained gameplay UI.
This makes plugin gameplay panels presentation-assembly independent while
allowing them to share the retained input, window, and DAT-sprite runtime.
Registrations made before the graphical host exists are buffered. The host
assigns `plugin:{pluginId}:{windowId}`, registers every panel with the common
window manager, persists its geometry/visibility, and exposes it through the
shared plugin sidepanel. Hiding/minimizing a panel does not dispose or pause the
plugin. No-window hosts retain the plugin session but expose the no-op UI
capability.
The retained markup vocabulary includes panels, nested groups, labels,
buttons, meters, tabs, lamp-style toggles, and scalar sliders. Controls bind to
BCL-visible properties/actions on the plugin binding object; visible controls
must correspond to real behavior, never placeholders that report success.
The following was the original pre-D.2b proposal and remains historical
context, not the shipped plugin contract:
@ -255,7 +266,8 @@ walk around / take damage / regen.
### Sprint 3 — Plugin API hardening (superseded shape)
- Document the `IPanel` contract.
- The shipped route is `IUiRegistry.AddMarkupPanel`, not plugin-owned
- The shipped route is `IUiRegistry.AddPanel` (with `AddMarkupPanel` as the
compatible legacy entry), not plugin-owned
`IPanel` implementations.
- Confirm plugins can subscribe to game events and expose retained markup
bindings without referencing App or ImGui assemblies.

View file

@ -0,0 +1,483 @@
# MossTank — VTank parity campaign
Date: 2026-08-26
Status: ACTIVE — MT1 USER-PASSED; MTUIMT9 functional/API scope complete; connected shelf/shell, accessibility, reconnect, bidirectional peer-expression and two-member fellowship gates passed; #451 root fixed and 30-minute dual-client activation soak passed; hostile/collision gates remain
Research baseline:
`docs/research/2026-08-26-mosstank-vtank-utilitybelt-research.md`
## Product definition
MossTank will provide the complete automation capability associated with
Virindi Tank, implemented as a first-class acdream plugin over a stable,
BCL-only plugin API. UtilityBelt's typed expression dialect is the scripting
baseline. Native file formats may differ; behavior and extensibility may not.
The finished surface is a visually verbatim VTank reproduction: every VTank
tab and function is present and every enabled control invokes real behavior.
## Non-negotiable boundaries
- modern code, behavior matched to documented VTank/retail behavior;
- one Runtime owner for every state/action; plugin API is a borrowed projection;
- policy engines remain in MossTank, not App or Runtime;
- plugin UI only through `IUiRegistry`;
- every API addition works in graphical and no-window hosts, with explicit
unavailable behavior until the host can genuinely supply it;
- no fake success and no silent expression-function omission.
## Slice ledger
### MT0 — research and campaign design
- [x] Reconcile existing VTank audit with current Runtime ownership.
- [x] Audit current UtilityBelt grammar and all 260 expression declarations.
- [x] Define complete capability ledger and staged architecture.
### MT1 — autocombat foundation (current stop gate)
- [x] Add target/combat views and attempt commands to the plugin API.
- [x] Project canonical hostile, selection, mode, power and spell state.
- [x] Implement target lock and range/angle/hybrid selection.
- [x] Implement melee/missile charge-release and direct offensive magic.
- [x] Deliver the polished combat dashboard and settings.
- [x] Focused, App/Runtime and complete solution gates.
- [x] Connected user gate: user confirmed autocombat works in the plugin.
MT1 intentionally does not pretend later features exist. It is “autocombat
ported,” not “all combat policy ported.”
### MTUI — generic plugin-window and VTank shell foundation
- [x] Add manifest-authenticated, stable plugin panel descriptors without
breaking API-v1 hosts/plugins.
- [x] Register plugin panels with the common retained window manager so
geometry and visibility persist.
- [x] Add the shared right-edge plugin shelf and window minimize/restore;
hidden panels leave the plugin session and automation running.
- [x] Add reusable nested groups, tabs, lamp toggles and sliders to retained
plugin markup.
- [x] Replace MossTank's dashboard/settings pair with one VTank-shaped shell
using the exact Options, Profiles, Vitals, Monsters, Items, Consumables,
Buffs, Route, Meta tab order.
- [x] Bind all currently enabled controls to real MT1/buff behavior and leave
unimplemented tabs visibly disabled.
- [x] Enable the Items/Consumables pages against durable, manifest-scoped
exact-name profiles; selection and Add/Add-no-buffs/Add-All-Peas controls
all mutate the policy consumed by combat.
- [x] Connected visual gate: shelf placement, minimize/restore persistence,
and first VTank-shell comparison in the live client.
### MT2 — complete monster/weapon/debuff combat policy
- [x] ordered `DEFAULT` + first-match monster rules;
- [x] priorities -1..4 and complete action-flag matrix;
- [x] damage/weapon/offhand selection, swap state machine and auto power,
including the official GameInfoDB exact-name overrides, ordered creature-
species preferences, and VTank's final elemental fallback;
- [x] debuff groups, skill/level choice, receipt-gated reapply and explicit
wand switching policy;
- [x] ring/arc/bolt density/range logic, streaks, Void, harm/martyr, grenades,
lenses, cast-on-strike and pets;
(carried phials are complete; crafting a missing phial belongs to MT4's
generalized craft transaction);
- [x] blacklist and both ghost-monster detectors, including canonical App
entity teardown for a detected client ghost.
MT2 checkpoint 2026-08-27: the BCL API now projects complete learned-combat
spell metadata, server cast and physical-attack receipts, health-update
revision/age, canonical equipment snapshots/commands, and exact-incarnation
ghost deletion. MossTank owns the complete Monsters expression/action model,
debuff tracker, elemental/shape spell catalog, range/density selection,
weapon/offhand policy, temporary blacklist, and both VTank ghost algorithms.
Focused evidence at this checkpoint: 104 MossTank tests, 20 Runtime action/
target tests, and an isolated Release App build all pass with zero failures or
warnings. MT2 remains open for automatic physical power and the four item-
backed combat families.
MT2 checkpoint 2 (2026-08-27): the official VTank assembly and live GameInfoDB
feed were inspected directly. Item appraisal SpellBooks are now retained;
plugins receive ordered combat chat and exact item UseDone receipts; the
source planner implements `dz.b.CompareTo` for SpellLevel/Skill preference;
the 72 official phials, lenses, cast-on-strike weapons and pets are executable
and profile-gated; proc success waits for the actual `You cast ... on ...`
line. `hi.cs` automatic attack power, including Recklessness clamping, is
ported verbatim. Items/Consumables profiles are atomically persisted through a
new per-manifest plugin-storage contract. Focused evidence: 131 MossTank tests,
the storage/chat/App tests, and an isolated Release App build pass. MT2 remains
open only for target-database `Auto` damage selection and the connected gate;
missing-grenade crafting is deliberately MT4 transaction scope.
MT2 automated closeout (2026-08-27): `Auto` now consumes the official 59-name
override and 103-species preference tables. The ordered element decision
outranks spell shape/tier, drives profiled physical weapon selection, and feeds
automatic attack power and vulnerability policy. Unknown targets preserve
VTank's final Pierce→Bludgeon→Slash→Acid→Lightning→Cold→Fire fallback. Focused
evidence after the closeout and named-profile foundation: 146 MossTank tests;
isolated Release App build 0 warnings / 0 errors. The connected MT2 combat
matrix remains part of the later combined user gate.
### MT3 — buff, heal and resource parity
- [x] named macro profiles, buff exclusions/item buffs/top-off foundation;
- [x] all three vital threshold tiers and canonical fellowship vitals;
- [x] profiled kits/consumables and worn-item mana recharge;
- [x] VTank ManaStone/ManaTank acquisition and exact-receipt fill behavior;
- [x] conversions, self/item/fellow dispel response, and critical/normal/idle
component plus six-category consumable upkeep.
### MT4 — inventory, craft and transactions
- [x] AutoStack/AutoCram and the official 757-row VTank craft database;
- [x] generalized use/apply/give/move/split/stack/drop transaction API with
receipts and busy arbitration;
- [x] retail 0x027D salvage and authoritative current-vendor sale paths;
- [x] same-input authoritative split crafting, all three split priorities and
exact VTank door/lockpick policy.
### MT5 — looting and extensible rule engine
- [x] corpse lifecycle/ID waits, exact 30-attempt/200-second open blacklist,
60-minute cache, 100-second public ownership, fellow Share Loot and rare-only
policy;
- [x] ordered first-match raw/projected-property expressions plus Keep,
KeepUpTo, Read, Salvage, Sell, ManaStone, ManaTank and User1User5;
- [x] canonical appraisal/pickup/salvage/vendor seams, unknown-scroll fallback,
and exact VTank salvage workmanship bands with 40-attempt abandonment;
- [x] independent By-char and named native loot profile documents;
- [x] exact VTClassic `.utl` v0/v1 importer/exporter, every structured
requirement, forward-compatible length blocks, and profile-owned salvage
ranges/value modes;
- [x] external loot-classifier plugin capability.
MT5 functional closeout (2026-08-27): the graphical host now exposes corpse
discovery, raw item properties, canonical appraisal/pickup, learned-spell
membership, fellowship Share Loot, retail salvage (0x027D), and current-vendor
sale through additive BCL-only interfaces. MossTank owns all policy and waits
for authoritative receipts/object removal; no action reports success at
dispatch. The official VTank corpse timers, rare/fellow ownership branches,
unknown-scroll difficulty check, mana-stone pairing, salvage-bag workmanship
bands, and bugged-bag retry ceiling were ported from the official decompiled
source. Focused evidence: 184 MossTank tests, 21 inventory-wire/session tests,
134 App automation/item/UI tests, and an isolated Release App build with zero
warnings/errors. File interoperability remains an MT9 compatibility tail, not
a reason to hold Route/Navigation.
### MT6 — navigation
- [x] canonical move/follow/turn/charged-jump/checkpoint host primitives;
- [x] circular, linear, once and Target/follow routes, including VTank's
endpoint reversal, destructive Once traversal and follow-around-corners;
- [x] every decoded nav node (0..9), closed-door/lockpick policy, vendor and
repeated NPC use, portal re-entry protection and combat/nav priority;
- [x] independent By-char and named native route profiles;
- [x] exact `uTank2 NAV 1.2` importer/exporter.
MT6 functional closeout (2026-08-27): the additive navigation API projects
VTank coordinates, live and server-accepted player position, object
reacquisition, door state, portal state, and typed movement levels through the
one Runtime command interpreter. MossTank owns the exact four route modes and
ten node types. Steering ports `fd.cs`'s 4° turn threshold, far 45° and near
15° forward cones; checkpoints use `gr.cs`'s accepted-position gate and
15-second nudge; Target mode ports `gl.cs` breadcrumb pruning; doors port
`b7.cs`'s defaults (disabled, 20 m ID, 4 m open, 50 lockpick threshold).
Portal2/UseNPC reacquire exact-name objects near the saved point, NPC use waits
for tell/give chat, jumps align to their stored heading before charge/release,
and Once removes completed rows exactly like VTank. Evidence: 204 MossTank
tests, focused App navigation projection tests, and isolated Release App build
with zero warnings/errors. Legacy file interop remains an MT9 compatibility
tail and does not hold the expression engine.
### MT7 — expressions
- [x] immutable AST, typed values, budgets and diagnostics;
- [x] UtilityBelt grammar semantics including lists/dicts/slices;
- [x] implement/alias/explicitly disposition the 260-function audit ledger;
- [x] VTank option/expression command diagnostics;
- [x] parser, evaluator, persistence and capability-security gates.
### MT8 — meta engine and runtime views
- [x] complete condition/action vocabulary, nested composition, once-per-entry,
call/return and watchdog;
- [x] chat capture variables and option access;
- [x] plugin-authored runtime views over the retained markup contract;
- [x] native meta profile;
- [x] exact VTank CondAct `.met` importer/exporter, including recursive rules,
embedded NAV and the historical CreateView record quirk.
### MT9 — fellowship, profiles, commands and polish
- [x] tell-driven recruitment, waiting-list, status/location commands, and
two-minute kick/ban/giveleader/setopen voting over canonical fellowship
commands;
- [x] helper healing, fellowship corpse permissions and shared target views;
- [x] macro-profile foundation: true per-character `By char` documents, named
create/copy/clear/select, mine-only filtering, hot loading, atomic manifest-
scoped storage, and complete current combat/buff/vitals/monster/item state;
- [x] independent navigation/loot/meta profile documents remain with MT5/MT6/MT8;
- [x] exact 137-name typed VTank option catalog/defaults and durable
`/vt opt setinall` across every indexed named/character macro profile;
- [x] all documented `/vt` command names are locally registered and handled;
- [x] exact `.nav`, `.met`, and `.utl` dumps/import-export;
- [x] privileged debug-operation semantics (`clearlocks`, `clearbusy`,
`fakeimp`) use canonical owners and authoritative lifetime cleanup;
- [x] first-run guidance, native/VTank profile migration and corrupt-profile
recovery with append-only raw-data preservation;
- [x] accessibility and scaling polish;
- [ ] performance soak, reconnect/lifecycle and multi-client gates.
## MT1 execution order
1. Add BCL-only combat records/interfaces with inert defaults.
2. Extend Runtime hostile query with exact position/heading snapshots.
3. Bind App's automation surface to the canonical action/spell owners.
4. Implement/test MossTank's deterministic combat controller.
5. Replace the small panel with dashboard/settings markup and generic markup
affordances needed by the design.
6. Run narrow tests, Release build, broad tests; record exact evidence here.
## Closeout evidence
MT1 code-complete 2026-08-26 and user-passed 2026-08-27. The additive BCL-only contract is
`CombatAutomation.cs`; older API-v1 implementations retain inert default
members. `AppAutomationSurface` borrows the canonical Runtime owners and
projects hostile captures, combat state, physical press/release attempts,
targeted casting and learned direct offensive spells. MossTank's
`CombatController` owns priority, target lock, range/angle/both selection,
mode entry, power-bar timing and magic choice. The dashboard/settings markup
uses the retained plugin registry; generic markup now supports bound child
visibility/enabled state and button colors.
Automated evidence:
- focused MossTank: 54 passed / 0 failed;
- complete Runtime: 1,854 passed / 0 failed;
- repository-owned hermetic Release gate: **15,775 passed / 0 skipped /
0 failed across 14 assemblies**;
- Release build: 0 warnings / 0 errors;
- the original MossTank XML documents parsed successfully before the gate.
MTUI code-complete 2026-08-27. `PluginPanelDescriptor` and authenticated
`PluginUiOwner` carry presentation metadata through Core's transactional
plugin lifetime; App mounts the stable panel as a `RetailWindowHandle` and the
generic `PluginSidePanel` owns only hide/restore UI. The one-window MossTank
shell uses real retained tabs/toggles/sliders. Focused evidence: 18 App/plugin
tests and 56 MossTank tests passed; isolated Release App build passed with
0 warnings / 0 errors. Broader hermetic evidence: Core 4,720/4,720 and Runtime
1,854/1,854 passed; App passed 6,441/6,442 with the sole failure in the
unrelated pre-existing landblock recenter assertion
`OriginRecenter_RetryPreservesLiveIdentityAndDoesNotRescueReusedGuid`. Its
connected visual gate remains open.
MT3/MT4 resource closeout 2026-08-27: crafting now runs through VTank's three
ordered tiers: critical component/consumable recovery, normal component and
general profile crafting, then no-target idle component and six-category
kit/food stock targets. Same-input recipes wait for both the authoritative
split receipt and publication of two distinct stacks before applying. The
official `IdleCraftCount_*` underscore names, 4/20/20 component defaults, and
2/2/2 kit plus 15/15/15 food targets persist in named/By-char profiles.
Self-cast and item dispels port `c8.cs`/`cx.cs`; fellowship Awakener selection
ports `af.cs`, including exact training, Arcane Lore, 5 m, spell-3179 and
summed-vulnerability-quality gates. The additive shared duration-spell ledger
matches VTank's confirmed local/external `LogSpellCast` model and clears on
session detach. Evidence: 277/277 MossTank tests, 12/12 focused App automation
tests, and isolated Release App build with zero warnings/errors.
MT7MT9 checkpoint 2026-08-27: MossTank registers all 260 audited
UtilityBelt public expression names over the typed evaluator, and the Meta
runtime/editor, dynamic views, embedded routes, command execution and durable
variable scopes are integrated. The host now provides an unload-safe generic
plugin-command registry; `/vt` follows the same local command route from typed
chat, launcher login commands and no-window clients. The exact official
four-line command catalog and 137-row typed option database are present;
`setinall` rewrites every indexed named/character profile. Run Macro is now a
master lifecycle distinct from Enable Combat, and command jumps align before
charging. The additive fellowship API projects the canonical retail commands;
MossTank owns VTank's tell commands, wait list, spam limit, near-player
recruitment, leader transition cleanup and two-minute voting. Evidence at this
checkpoint: 261/261 MossTank tests, 18/18 runnable focused App/plugin tests,
and isolated Release App build with zero warnings/errors. Four additional
GraphicalPluginSession tests could not locate the repository when deliberately
run from an isolated OutputPath; this is test-harness path behavior, not a
product failure. Connected shelf/UI/fellowship and combined automation gates
remain open.
Legacy-profile checkpoint 2026-08-27: native JSON remains MossTank's durable
working format, while every save also emits a genuine VTank compatibility
file. `uTank2 NAV 1.2` routes and CondAct `.met` files round-trip exactly;
the Meta writer was independently accepted and canonicalized byte-identically
by the public `metaf` reference compiler. VTClassic `.utl` v0/v1 now retains
length-delimited unknown requirements/blocks, executes all 31 published
requirement types (including the DAT-resolved ordered-palette color family),
and applies per-material salvage ranges/value modes to the real 0x027D combine
planner. Native-only text rules export disabled rather than becoming
VTClassic's dangerous empty-requirement match-all. Evidence: 290/290 MossTank
tests, 13/13 focused App/plugin tests, and isolated Release App build with zero
warnings/errors.
External-loot checkpoint 2026-08-27: the BCL-only host now owns an unload-safe
classifier registry. Classifier ids are namespaced to the registering plugin,
all registrations are disposed transactionally with that plugin's session,
and exceptions are isolated at the registry boundary. MossTank exposes the
available engines in Profiles, persists the selection with the macro profile,
and runs Keep/KeepUpTo/Read/Salvage/Sell/User1User5 decisions through its
existing authoritative corpse executor. An unavailable engine never silently
changes policy by falling back to VTClassic. Evidence: 2 focused Core registry
tests, 55 focused MossTank loot/panel/markup tests, and isolated Release App
build with zero warnings/errors.
Options/debug checkpoint 2026-08-27: the VTank Options page now uses the
verbatim four-column control arrangement. Normal automatic rebuff, the
separate idle top-off window, Attack→Approach distance navigation, and final
Idle Peace fallback were ported from `fz.cs`, `cLogic.cs`, `g8.cs`, `eb.cs`
and `cm.cs`; Force Buff and Cancel Force Buff remain distinct actions. The
Advanced Options button opens the full ordered 137-setting table. `/vt
clearbusy` decrements exactly one Runtime-owned inventory busy reference,
`clearlocks` clears only MossTank's transient policy locks, and `fakeimp`
records VTank's local 3,000-second Gossamer Flesh debug marker without forging
a server cast. External classifiers now receive authoritative `OnLooted` and
`OnItemRemoved` lifecycle callbacks after inventory publication. Evidence:
298/298 MossTank tests and an isolated Release App build with zero warnings
and zero errors.
Final automated API/options checkpoint 2026-08-27: every one of the 137
official advanced-option names has an explicit writable live-policy mapping;
the full catalog, official defaults, case-insensitive lookup and durable
profile propagation are covered. The Monsters page now exposes the three
distinct official cycles for Damage type, Ex. Vuln and PetDmg rather than one
shared internal enum. Prismatic remains an ammunition policy while preserving
automatic magic-element selection; Fists uses Tusker Fists only while its
enchantment is active. `DoJiggle` now ports VTank's PreviousSelection followed
by alternating NextPlayer/PreviousPlayer at 131 ms and no longer moves the
character. `ShowCollisionDebug` publishes bounded projectile samples through
the BCL-only API and renders transient red/green markers in the retained UI.
`WhoYouGonnaCall` is intentionally stored but inert, matching the official
source's explicit `No Function` disposition.
The plugin API now projects combat, magic, equipment/items, looting,
fellowship, enchantments, navigation, world objects/time, login, network peer
state, recovery, projectile diagnostics and selection through canonical
Runtime/App owners. Startup peer tags are parsed once by `RuntimeOptions`,
portable data paths come from `ApplicationPathSet`, and both graphical and
headless plugin hosts load fixtures correctly from isolated output graphs.
Latest hermetic evidence: App 6,592 passed / 94 environment-dependent skips;
Runtime 1,863/1,863; Core 4,911/4,911; Core.Net 1,042/1,042; Headless
171/171; UI abstractions 880/880; MossTank 320/320 — **15,779 passed, zero
failed** across the selected automated lanes. The Release App build completed
with zero warnings and zero errors. Excluded gates are explicit: manual/live
lanes, Linux-only tests on this Windows host, the machine-local stale bake-tool
4 PAK test, and one registered pre-existing tower-ascent known failure. The
generic shelf, VTank shell, minimization-while-running, reconnect, live combat,
multi-client peer expressions, and collision-marker appearance remain owed in
the combined connected user gate.
Connected shelf/shell gate 2026-08-27: the first isolated Release launch found
that App's plugin-copy target still assumed each plugin's conventional `bin`
directory when a custom `OutputPath` was active. That caused the packaged
MossTank DLL/markup to be stale even though the root build outputs were current.
Build and publish now resolve both first-party plugin targets through MSBuild's
`GetTargetPath`; MossTank markup copies directly from its source. The rebuilt
package's MossTank DLL and XML matched their build/source SHA-256 hashes and
the boundary regression passed 5/5.
The next live launch exposed a retained-markup contract mismatch: one field
reused an `Action` button binding where `onsubmit` requires `Action<string>`,
preventing the complete plugin window from mounting. MossTank now has a typed
submit action and its markup contract test validates every interactive binding's
delegate shape. A later visual pass also caught three unsupported inline label
bindings on Meta; all are now whole-value properties, and the contract rejects
future inline interpolation. Focused MossTank evidence is 321/321; isolated
Release build `app-release22` is zero-warning/zero-error with exact packaged
artifact hashes.
The connected `app-release22` gate then passed: all nine tabs mounted and were
visually inspected; Meta rendered `State: Default`, `N: 0`, and `N2: 0`; the
right-edge `MT` shelf button was fully reachable; minimize hid only the window;
while hidden the live buff pass advanced from 91/97 to 77/97; restore showed
`Stop Macro` and the changed live status; the macro stopped normally. Logs show
92 server-confirmed `UseDone err=0` casts and no plugin/UI exception. Shift+Esc
completed the full logout presentation and returned to character selection.
This supersedes the earlier statement that the shelf, shell, minimization, and
basic reconnect/lifecycle presentation were wholly unproven. At that checkpoint,
still owed were
the accessibility/scale closeout, longer performance/reconnect soak, live
hostile combat matrix, two-client peer expressions/fellowship, and collision-
marker appearance.
Accessibility/reconnect/peer checkpoint 2026-08-27: textless and terse controls
now carry runtime-bound retained tooltips, and the common window owner clamps
plugin panels to the current viewport (including the 800x600 oversize case).
Focused evidence is 325/325 MossTank tests, 16/16 retained-UI tooltip/geometry
tests, and isolated Release `app-release23` with zero warnings/errors. The live
client displayed the Monster Range help text, completed a same-character
logout/re-entry, restarted the macro, and completed another 92 server-confirmed
casts. Working/private memory stayed approximately 1.59/1.84 GiB across the
combined soak rather than climbing with casts or reconnect.
The local peer API also passed real two-process expressions in both directions:
the secondary `+Horan` evaluated
`dictgetitem[listgetitem[netclients['mosstank-guard-primary'],0],'Name']` and
received `+Acdream`, while the earlier reciprocal gate returned `+Horan` to
the primary; both heartbeat documents contained the expected names, tags,
vitals and positions.
That broader gate exposed separate client defect #451. First-chance cdb proof
located it in GLFW's Win32 event pump: temporary cross-process input-queue
attachment let `GetActiveWindow` return the other acdream process's HWND;
GLFW's shared `L"GLFW"` property then returned the other process's private
`_GLFWwindow*`, which the caller dereferenced. `app-release24` installs the
current-process HWND guard at GLFW's own import slot before `glfwInit`; its four
focused tests pass. Two rebuilt graphical clients then entered world, survived
100 rapid forced activation switches—the exact old trigger—and remained
responsive through a 30-minute combined soak with no native error. Issue #451
remains in-progress only until both sessions complete a graceful-exit gate.
The secondary-owned fellowship gate also passed: `+Acdream` created
`mosstankgate`, `+Horan` joined, both canonical rosters contained both members,
and the secondary evaluated `getfellowshipcount[]` as `2`.
Still owed here: the hostile combat matrix and collision-marker appearance.
Final local validation checkpoint 2026-08-27: the complete Release solution
build passed with zero warnings and zero errors. Focused MossTank passed
325/325 and the App plugin/API/UI/GLFW set passed 35/35. The conservative
Windows hermetic filter passed 15,083 non-network tests; Core.Net then passed
1,042/1,042 in its isolated lane, for 16,125 passing selected tests. The first
max-parallel combined invocation made Core.Net's timing-sensitive two-percent
packet-loss soak exhaust its wall-clock headroom; the same case and complete
Core.Net lane passed immediately when isolated. No MossTank, plugin API, plugin
UI, Runtime-owner, or #451 guard test failed.
Live hostile discovery checkpoint 2026-08-27: the first surrounded-monster
gate exposed two coupled compatibility defects. Retail's classic `* Lure`
vulnerability names were absent from the debuff classifier, so an attack-only
profile could misclassify Piercing Lure's "piercing damage" description as a
direct attack. The classifier now recognizes all seven classic elemental Lure
families (while excluding the distinct Lure Blade item spell), and the attack
catalog defensively rejects every host-authored debuff. Target evaluation also
now ports official `dz::a`'s previous-target tie-break after priority and manual
TargetLock: a valid chosen monster remains selected while the character turns,
instead of angle rescans alternating between surrounding monsters. The new
Lure/attack and target-stability regressions bring the focused MossTank lane to
337/337. Connected re-test remains part of the hostile combat gate.
## Requirement-level completion audit (2026-08-27)
Completion is deliberately **not** claimed while live evidence remains missing.
The authoritative requirement/evidence map is:
| Objective requirement | Current evidence | Audit result |
| --- | --- | --- |
| Functionally complete VTank behavior | MT2MT9 implementation ledger; 337 MossTank behavior/format/expression tests; connected MT1 autocombat acceptance | Proven for implemented policy and formats; the combined hostile physical/magic matrix remains live-unproven |
| Visually verbatim nine-tab VTank surface | `mosstank.xml` contains the exact Options, Profiles, Vitals, Monsters, Items, Consumables, Buffs, Route, Meta order; all nine tabs mounted in `app-release22` | Proven for shell/tab presence and first comparison; projectile debug-marker appearance remains live-unproven |
| Every visible control has real behavior | 190 interactive controls expose 202 bindings (191 unique); `MossTankMarkupContractTests` resolves every binding, verifies delegate shape, and rejects handlerless controls; 137/137 advanced options have explicit writable mappings | Proven statically and by focused controller tests. `WhoYouGonnaCall` intentionally stores its value but performs no action because the official VTank source labels it `No Function` |
| Generic plugin sidepanel; minimizing must not stop plugins | retained `PluginSidePanel`/window-manager tests plus connected hide/restore gate where the hidden buff pass advanced from 91/97 to 77/97 | Proven |
| Modern acdream plugin APIs over canonical owners | additive BCL-only combat, magic, equipment, item, loot, fellowship, enchantment, navigation, object, world-time, login, network, recovery, projectile, selection, storage, command and classifier contracts; 35 focused App/API/UI tests and 16,125 selected Release tests | Proven for the graphical live host; older/no-window implementations explicitly report unavailable and never fabricate success |
| UtilityBelt-compatible expression superset | immutable evaluator tests; all 260 audited public names registered; host-action, object, fellowship, time, login/network, UI, persistence, collection and meta tests | Proven by catalog and semantic family tests; bidirectional two-client network expressions passed live |
| Lifecycle, reconnect, multi-client stability | same-character reconnect and hidden execution passed; peer expressions and two-member fellowship passed; #451 exact trigger survived 100 focus switches and a 30-minute dual-client soak | Proven through soak; #451 cannot close until both current sessions exit gracefully |
Open completion gates: (1) hostile physical and offensive-magic behavior against
a live target at valid configured range; (2) visible green/red projectile
collision markers with `ShowCollisionDebug`; (3) graceful exit of both current
soak clients with no native or managed failure. These are evidence gaps, not
redefined-away acceptance criteria.

View file

@ -0,0 +1,447 @@
# 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.

View file

@ -0,0 +1,95 @@
# MossTank autocombat design
Date: 2026-08-26
## Outcome
Ship the first VTank-class MossTank milestone: a polished in-client controller
that performs safe automatic melee, missile or direct-spell combat while all
policy remains in the plugin and all authoritative state/actions remain in
Runtime.
## Architecture
```text
Runtime canonical owners
entity directory + object table + selection + combat + spellbook
|
v
AppAutomationSurface (borrowed projection, no ownership)
PluginCombatTarget[] + PluginCombatSnapshot + attempt commands
|
v
MossTank CombatController (policy/state machine)
scan -> score/lock -> mode -> charge/cast -> wait -> repeat
|
v
retained plugin panel (bindings only)
```
`AcDream.Plugin.Abstractions` stays BCL-only. New interfaces use records,
enums, arrays/lists and primitives only. Existing interfaces gain default
members where needed so API v1 plugins remain loadable.
## API additions
- `PluginCombatTarget`: id, name, weenie class, distance, signed relative
angle, health-known and health fraction.
- `PluginCombatSnapshot`: selected id, mode, charge/request state, power and
server-pending state.
- `ICombatAutomation`: immutable hostile snapshot plus explicit mode,
begin/release/abort attempts.
- `ISpellCatalog.KnownAttackSpells`: learned, direct offensive spells.
- `IAutomationSurface.Combat`: the combat group.
Attempt results distinguish unavailable, invalid target, wrong mode, busy,
transition started and sent/started. This avoids `bool` APIs whose `false`
cannot tell a plugin whether to wait, retry, reselect or stop.
## Target snapshots
`RuntimeHostileTargetQuery` is extended with a snapshot capture method. It
borrows the same entity directory and `ClientObjectTable` used by gameplay,
filters with the same `CombatTargetPolicy`, and computes distance and relative
heading using retail's `MoveToMath` helpers. Hidden, no-draw, dead and
cell-less entities are excluded. The App surface refreshes at bounded cadence
and publishes one immutable list reference; retained UI reads do not scan the
world or allocate.
## Combat controller
States:
1. `Off`: no automation command may be emitted.
2. `Acquire`: keep a valid lock or choose the lowest score.
3. `Mode`: request the equipped default combat mode and wait for confirmation.
4. `PhysicalCharge`: select, set power, press height, then wait until the
canonical meter reaches desired power before release.
5. `MagicCast`: select and cast the chosen known offensive spell.
6. `Wait`: wait while physical server response, repeat state or magic busy is
active, then reacquire/repeat.
Target scoring first applies ordered rules (initial slice supplies a default
priority and an ignore-name list), then applies the configured selection
method. Target lock keeps the current target while it remains admissible.
The controller never fabricates success. Health and disappearance retire a
target; timeouts return to `Acquire`; session loss transitions to `Off` and
aborts an in-progress physical build.
## UI
The main window becomes a dashboard rather than a single force-buff button:
macro toggle, current target/mode, state, vitals, combat settings, buff action
and settings navigation. Generic markup gains bound child visibility/enabled
and color/style attributes so active controls read as active without App types
leaking into the plugin.
## Verification
- pure controller tests for selection policies, lock, mode transition,
charge/release, busy suppression, magic choice, disable and session loss;
- Runtime query tests for filter, range, distance, relative angle and health;
- App projection tests for caching and command mapping where practical;
- markup parser tests for new generic bindings;
- MossTank, Runtime, App and complete Release solution gates.