feat(vtank): slice 7 fix round A — grid scaling, Profiles cleanup, popups as own panels

Owner's live-client report (2026-09-07 08:05): "The Options tab labels
overlap, fix that" and "Those BIG gold/yellow buttons HAS to go" (the
gold-button half already landed upstream at cfa703065, merged into this
branch first). This commit is the geometry half of that fix round plus
the lead's 07:55 read (docs/plans/2026-09-07-campaign-vt-slice7-tabs.md).

Grid scaling: measured acdream's actual retail DAT font (0x40000000) via
AcDream.Cli's dump-font-atlas against a real installed DAT rather than
guessing — MaxCharHeight=16 matches VVS's own assumed row height exactly,
so no vertical scale was needed, but several captions render wider than
VVS's narrow font assumed ("Follow/Nav Min Distance:" measures 134px real
width vs an 80px-wide retail label column). <label>/<toggle> never clip or
use their declared "w" (UiLabel.OnDraw/UiMarkupToggle.OnDraw draw raw,
unclipped text), so the fix is a plain rightward SHIFT of the columns after
the overflowing captions rather than a width change: Options' columns 2-5
move +62px (90/220/386/552/611 -> 152/282/448/614/673, with the rightmost
Advanced Options/Run Macro/status sub-block also shrinking its declared
width 233->175 to stay inside the group's 848 budget), Profiles' columns
2-5 move +16px (140/280/330/440/530 -> 156/296/346/456/546). Both are pure
translations, so every original inter-column gap is preserved exactly.
Vitals needed no shift (its worst caption already clears its slider by 3px)
but got a small +4px safety margin anyway since it was the tightest
already-OK case measured.

Profiles leftovers deleted (owner + lead, "legacy controls"): the three
stacked ProfileNameDraft/RouteProfileNameDraft/MetaProfileNameDraft
field+"New"-button rows (three 20-tall fields at zero vertical gap that
rendered as one solid black bar behind three stacked "New" buttons — the
owner's screenshot), the acdream-only "Loot engine" classifier row
(no VTank equivalent), and the ProfileLifecycleNotice label (the raw
macro-profile recovery path the owner saw rendered). Deviation: Macro/Nav
CopyTo lose their only in-UI target-name entry point (Meta's naming
already exists on the Meta tab itself, so only Macro/Nav are affected) —
an accepted trade for matching VTank's own Profiles table, which has no
name-draft control at all (VVS's own combo is presumably editable
in-place; acdream's <menu> is not). LootStatus is kept (general feedback,
not itself a reported leftover) but repositioned into the freed space.

Window: Advanced Options and Loot Editor move out of mosstank.xml into
their own top-level plugin panels (mosstank-advanced.xml,
mosstank-loot-editor.xml, MossTankPlugin.cs registers a second and third
AddPanel with ShowInSidePanel=false since VTank's own popups have no
shelf entry). No new IUiRegistry show/hide API was needed: a root <panel>
already supports a data-bound visible="{Prop}" attribute
(MarkupDocument.Build re-evaluates VisibleSource every frame, proven by
the main panel's own visible="{WindowAvailable}"), so each popup's own
visible="{AdvancedOptionsVisible}"/"{LootEditorVisible}" is the SAME flag
the Options/Profiles checkbox flips and the popup's own "Back" button
clears — closing the popup unchecks the box for free. The main window
returns to VTank's 210-ish proportion (236 = 42px header/tab-strip +
194px per-tab content, both unchanged since S7.1) with no dead space
below the tabs. The eight tab-content *Visible getters (MossTankPanel.cs)
drop their `&& !_lootEditorVisible`/`&& !_advancedOptionsVisible` guards,
which only existed because the popups used to share screen space with the
tab content in the same window.

Trailing blank button slots (Route waypoint reorder, Meta rule reorder,
Loot Editor rule reorder): the retail default UI font (0x40000000) has no
glyph for U+2191/U+2193, so the "↑"/"↓" text buttons rendered as blank
slots (owner's screenshot). Replaced with the same DAT move-up/move-down
icons (0x060028FC/0x060028FD) the Monsters grid's own reorder columns
already use.

No remaining <menu style="retail"> in any of the three files (verified).

Pins updated (shown to fail against the old numbers first): the markup
contract's interactive-control count (202 -> 167, the two popups leaving
the file plus the Profiles deletions) and the authored-shell height pin
(350 -> 236). Added SecondaryPopupPanelsFitTheirOwnBoundsAndEveryBindingResolves
(parses both new files, checks their declared 392x300/268x300 footprint,
AssertWithinParent, and every markup binding resolves) and
NoButtonAnywhereUsesTheUnrenderableArrowGlyphs (no <button text="↑"|"↓">
survives anywhere) as new coverage for this fix round.

MossTank suite 651 -> 654 (the two new SecondaryPopupPanels theory cases
+ NoButtonAnywhereUsesTheUnrenderableArrowGlyphs); App markup/plugin/menu
filter stays 237/237.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 09:09:41 +02:00
parent 045cd0a195
commit 565a33d789
8 changed files with 434 additions and 231 deletions

View file

@ -22,6 +22,12 @@
<None Update="mosstank.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="mosstank-advanced.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="mosstank-loot-editor.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<EmbeddedResource Include="VtankCraftRecipes.tsv" />
<EmbeddedResource Include="VtankAmmunitionOptions.tsv" />
<EmbeddedResource Include="VtankDefaultSettings.usd" />

View file

@ -347,17 +347,23 @@ internal sealed partial class MossTankPanel
public bool RouteTabEnabled => true;
public bool MetaTabEnabled => true;
public bool OptionsVisible => OptionsSelected
&& !_lootEditorVisible
&& !_advancedOptionsVisible;
public bool ProfilesVisible => ProfilesSelected && !_lootEditorVisible;
public bool VitalsVisible => VitalsSelected && !_lootEditorVisible;
public bool MonstersVisible => MonstersSelected && !_lootEditorVisible;
public bool ItemsVisible => ItemsSelected && !_lootEditorVisible;
public bool ConsumablesVisible => ConsumablesSelected && !_lootEditorVisible;
public bool BuffsVisible => BuffsSelected && !_lootEditorVisible;
public bool RouteVisible => RouteSelected && !_lootEditorVisible;
public bool MetaVisible => MetaSelected && !_lootEditorVisible;
// Fix round A (2026-09-07): Advanced Options / Loot Editor used to be
// in-panel groups sharing this SAME window with the nine normal tabs,
// so a tab's own content had to hide while a popup was open to avoid
// drawing both at once. Now that both popups are their own separate
// plugin panels (MossTankPlugin.cs, mosstank-advanced.xml /
// mosstank-loot-editor.xml), they no longer share screen space with
// any tab body, so these guards are gone — a tab stays visible
// (or not) purely by tab selection again.
public bool OptionsVisible => OptionsSelected;
public bool ProfilesVisible => ProfilesSelected;
public bool VitalsVisible => VitalsSelected;
public bool MonstersVisible => MonstersSelected;
public bool ItemsVisible => ItemsSelected;
public bool ConsumablesVisible => ConsumablesSelected;
public bool BuffsVisible => BuffsSelected;
public bool RouteVisible => RouteSelected;
public bool MetaVisible => MetaSelected;
public bool LootEditorVisible => _lootEditorVisible;
public bool AdvancedOptionsVisible => _advancedOptionsVisible;
public IReadOnlyList<string> AdvancedOptionNames => VtankOptionCatalog.Names;

View file

@ -52,6 +52,38 @@ public sealed class MossTankPlugin : IAcDreamPlugin
Path.Combine(directory, "mosstank.xml"),
_panel);
// Fix round A (2026-09-07): Advanced Options / Loot Editor were
// in-panel groups sharing the main window (forcing it to 350px
// tall with dead space under the nine normal tabs); they are now
// their own plugin panels, matching VTank's own separate popup
// windows (docs/research/vtank-kb/08-ui-views.md §1's secondary-
// view table). ShowInSidePanel=false — VTank's popups have no
// independent shelf/taskbar entry, they only exist while opened
// from the Options/Profiles tab checkbox
// (AdvancedOptionsVisible/LootEditorVisible). Each popup's own
// <panel visible="{...}"> binding is the SAME flag the checkbox
// flips and the popup's own "Back" button clears, so closing the
// popup unchecks the box for free — no new IUiRegistry show/hide
// API needed (MarkupDocument.Build already re-evaluates a root
// <panel>'s VisibleSource every frame; see the two files' own
// header comments).
_host.Ui.AddPanel(
new PluginPanelDescriptor("advanced-options", "MossTank Advanced Options")
{
StartVisible = false,
ShowInSidePanel = false,
},
Path.Combine(directory, "mosstank-advanced.xml"),
_panel);
_host.Ui.AddPanel(
new PluginPanelDescriptor("loot-editor", "MossTank Loot Editor")
{
StartVisible = false,
ShowInSidePanel = false,
},
Path.Combine(directory, "mosstank-loot-editor.xml"),
_panel);
_commandRegistration = _host.Commands.Register(
"vt",
_panel.ExecuteVtankCommand);

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Advanced Options popup — VTank's own AdvancedOptionsView is a separate
392x300 OS-level window (docs/research/vtank-kb/08-ui-views.md §1's
secondary-view table), opened/closed by the Options tab's own
"Advanced Options" checkbox (mosstank.xml, cShowAdvanced/
ToggleAdvancedOptionsVisible). Fix round A (2026-09-07) moved this OUT of
the main mosstank.xml panel (where it used to live as an in-panel group
sharing the main window, forcing the whole window to grow to 350px tall
with ~180px of dead space under the nine normal tabs) into its own
top-level plugin panel, registered as a SECOND AddPanel call from
MossTankPlugin.cs with ShowInSidePanel=false (VTank's popup has no
independent shelf/taskbar entry — it only exists while opened from the
checkbox). This panel's own visible="{AdvancedOptionsVisible}" binding is
the SAME flag the checkbox flips and this popup's own "Back" button clears
(HideAdvancedOptions => _advancedOptionsVisible = false) — closing the
popup unchecks the box for free, no new host show/hide API needed:
MarkupDocument.Build already re-evaluates a root <panel>'s VisibleSource
every frame (proven by the main panel's own visible="{WindowAvailable}").
VTank's own popup has no Apply/Back buttons at all (edits are
destructive-live in retail); acdream keeps its own explicit Apply/Back
pair since our draft-then-submit editing flow needs them.
-->
<panel x="440" y="60" w="392" h="300" title="MossTank Advanced Options"
visible="{AdvancedOptionsVisible}" resize="none">
<label x="4" y="4" w="384" h="16"
text="Advanced Options — complete VTank settings table"
color="#FFE8DEC3" />
<list x="4" y="22" w="260" h="160" rowheight="17"
items="{AdvancedOptionNames}" selected="{SelectedAdvancedOptionIndex}"
onchange="{SelectAdvancedOption}"
tooltip="Select one of Virindi Tank's 137 advanced options." />
<label x="272" y="22" w="116" h="16" text="{AdvancedOptionName}" color="#FFE8DEC3" />
<button x="272" y="42" w="116" h="23" text="Apply"
onclick="{ApplyAdvancedOption}" />
<button x="272" y="70" w="116" h="23" text="Back"
onclick="{HideAdvancedOptions}" />
<field x="4" y="188" w="260" h="23" text="{AdvancedOptionValueDraft}"
onchange="{SetAdvancedOptionValueDraft}" onsubmit="{SubmitAdvancedOption}"
maxlength="160" clearonsubmit="false" background="#E6000000"
tooltip="Edit the selected advanced-option value and press Enter or Apply." />
<label x="4" y="216" w="384" h="16" text="{AdvancedOptionNotice}" color="#FFC7B98F" />
</panel>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Loot Editor popup — VTank's own FriendlyLootEditorView is a separate
268x300 OS-level window (docs/research/vtank-kb/08-ui-views.md §1's
secondary-view table) whose retail "Rules" page is a literal stub ("This
page not yet completed"); our own editor is genuinely more complete
(KB §5). Opened/closed by the Profiles tab's own "Show Editor" checkbox
(mosstank.xml, cShowLootEditor/ToggleLootEditorVisible).
Fix round A (2026-09-07) moved this OUT of the main mosstank.xml panel
(an in-panel group forcing the whole window to grow to 350px tall with
dead space under the nine normal tabs) into its own top-level plugin
panel, registered as a THIRD AddPanel call from MossTankPlugin.cs with
ShowInSidePanel=false (VTank's popup has no independent shelf entry). This
panel's own visible="{LootEditorVisible}" binding is the SAME flag the
checkbox flips and this popup's own "Back" button clears
(CloseLootEditor => _lootEditorVisible = false) — closing the popup
unchecks the box for free (see mosstank-advanced.xml's comment for why no
new host show/hide API was needed).
Fix round A also replaced the "↑"/"↓" text buttons (MoveLootRuleUp/Down)
with the same DAT move-up/move-down icon glyphs the Monsters grid already
uses (0x060028FC / 0x060028FD) — the retail default UI font (0x40000000)
has no glyph for U+2191/U+2193, so these rendered as blank button slots
(owner's 07:55 screenshot read).
LootEditorNotice (a status label, not an action) is the one control
dropped to make everything else fit without clipping — a deliberate,
narrow trim given how much richer this editor is than the 268-wide popup
VTank itself budgets for.
-->
<panel x="440" y="60" w="268" h="300" title="MossTank Loot Editor"
visible="{LootEditorVisible}" resize="none">
<label x="4" y="2" w="190" h="16" text="Loot profile rules (first match wins)"
color="#FFE8DEC3" />
<button x="198" y="2" w="64" h="18" text="Back"
onclick="{CloseLootEditor}" />
<field x="4" y="22" w="260" h="18" text="{LootProfileNameDraft}"
onchange="{SetLootProfileNameDraft}"
onsubmit="{CreateNamedLootProfile}" maxlength="64"
background="#E6000000" color="#FFE8DEC3"
tooltip="Name a new or copied loot profile." />
<button x="4" y="44" w="58" h="18" text="New"
onclick="{CreateLootProfile}" />
<button x="66" y="44" w="58" h="18" text="CopyTo"
onclick="{CopyLootProfile}" />
<button x="128" y="44" w="58" h="18" text="Clear"
onclick="{ClearLootProfile}" />
<button x="190" y="44" w="70" h="18" text="Delete"
onclick="{DeleteLootProfile}"
tooltip="Delete the selected named loot profile's real .utl file. Not available for By char." />
<list x="4" y="66" w="260" h="68" rowheight="17"
items="{LootRuleRows}" selected="{SelectedLootRuleIndex}"
onchange="{SelectLootRule}" tooltip="Select an ordered loot rule to edit." />
<menu x="4" y="138" w="260" h="18" items="{LootActionNames}"
selected="{SelectedLootAction}" onchange="{SelectLootAction}"
rows="9" scroll="true" tooltip="Choose the action for matching loot." />
<label x="4" y="160" w="140" h="16" text="{LootPriorityText}" color="#FFC7B98F" />
<button x="148" y="160" w="22" h="18" text="-"
onclick="{LootPriorityDown}" tooltip="Decrease loot rule priority." />
<button x="174" y="160" w="22" h="18" text="+"
onclick="{LootPriorityUp}" tooltip="Increase loot rule priority." />
<label x="4" y="180" w="140" h="16" text="{LootKeepCountText}" color="#FFC7B98F" />
<button x="148" y="180" w="22" h="18" text="-"
onclick="{LootKeepCountDown}" tooltip="Decrease the KeepUpTo count." />
<button x="174" y="180" w="22" h="18" text="+"
onclick="{LootKeepCountUp}" tooltip="Increase the KeepUpTo count." />
<field x="4" y="200" w="190" h="18" text="{LootExpressionDraft}"
onchange="{SetLootExpressionDraft}" onsubmit="{ApplyLootExpression}"
maxlength="256" background="#E6000000" color="#FFE8DEC3"
tooltip="VTClassic or UtilityBelt-compatible loot match expression." />
<button x="198" y="200" w="64" h="18" text="Apply"
onclick="{ApplyLootRule}" />
<button x="4" y="222" w="54" h="18" text="Add"
onclick="{AddLootRule}" />
<button x="60" y="222" w="64" h="18" text="Remove"
onclick="{RemoveLootRule}" />
<button x="128" y="222" w="26" h="18" icon="0x060028FC" iconkind="did"
onclick="{MoveLootRuleUp}" tooltip="Move the selected loot rule up." />
<button x="158" y="222" w="26" h="18" icon="0x060028FD" iconkind="did"
onclick="{MoveLootRuleDown}" tooltip="Move the selected loot rule down." />
<toggle x="4" y="244" w="128" h="18" text="Loot All Corpses"
checked="{LootAllCorpsesEnabled}" onclick="{ToggleLootAllCorpses}" />
<toggle x="136" y="244" w="128" h="18" text="Loot Fellow Corpses"
checked="{LootFellowCorpsesEnabled}"
onclick="{ToggleLootFellowCorpses}" />
<toggle x="4" y="264" w="128" h="18" text="Loot Only Rare Corpses"
checked="{LootOnlyRareCorpsesEnabled}"
onclick="{ToggleLootOnlyRareCorpses}" />
<toggle x="136" y="264" w="128" h="18" text="Read Unknown Scrolls"
checked="{ReadUnknownScrollsEnabled}"
onclick="{ToggleReadUnknownScrolls}" />
<label x="4" y="282" w="140" h="16" text="{LootRangeText}" color="#FFC7B98F" />
<button x="148" y="282" w="22" h="16" text="-"
onclick="{LootRangeDown}" tooltip="Decrease corpse-looting range." />
<button x="174" y="282" w="22" h="16" text="+"
onclick="{LootRangeUp}" tooltip="Increase corpse-looting range." />
</panel>

View file

@ -7,16 +7,50 @@
<!--
Campaign VT slice 7 S7.1: the content area is 856 wide, matching VTank's
own mainView.xml (856x210, docs/research/vtank-kb/08-ui-views.md §0). The
panel is taller than VTank's own 210 (350, not 210) ONLY because the
Advanced Options and Loot Editor pages moved to VTank's own POPUP
geometry (392x300 / 268x300, KB §1's secondary-view tables) as separate
groups within this same window (KB §5's documented in-panel-popup design
choice) rather than VTank's own separate OS-level popup windows — the
nine normal tab bodies below keep their existing 194-tall content
(== VTank's 210 minus its own 16px HudTabView tab strip, Float_Theme.cs's
TabHeight) and simply don't fill the extra vertical room the popups need.
Advanced Options and Loot Editor pages now live in their OWN plugin panels
(mosstank-advanced.xml / mosstank-loot-editor.xml, registered by
MossTankPlugin.cs at VTank's own 392x300 / 268x300 popup geometry — KB §1's
secondary-view tables), toggled by the SAME AdvancedOptionsVisible /
LootEditorVisible flags this panel's own checkboxes flip, via each popup's
own <c>&lt;panel visible="{...}"&gt;</c> binding — so the main window is
back to VTank's 210-tall proportion (42 header/tab-strip + 194 content,
unchanged since S7.1) with no dead space below the tabs.
Fix round A (2026-09-07, owner: "Options tab labels overlap" + lead's
07:55/08:05 read): VTank's literal L,T,W,H assume an ~11px VVS font;
measured against acdream's actual retail DAT font 0x40000000
(built via `AcDream.Cli`'s `dump-font-atlas` command against a real
installed DAT: MaxCharHeight=16, matching VVS's own assumed row
height exactly, so no VERTICAL scale is needed) some captions render wider
than VVS's box: "Follow/Nav Min Distance:" measures 134px real width vs
an 80px-wide retail label column, overflowing into the checkbox column 2px
to its right. <label>/<toggle> never clip or use their declared "w" for
rendering (UiLabel.OnDraw/UiMarkupToggle.OnDraw both draw raw, unclipped
text — only <list>/<field>/<menu> boxes are real containers), so the fix
is a plain rightward SHIFT of every column after the labels, not a width
change: OPTIONS_COL_SHIFT = 62px moves every Options control whose retail
x >= 90 to x+62 (90->152, 220->282, 386->448, 552->614, 611->673),
clearing the widest real caption (134px @ x=8, ends 142; column 2 now
starts 152) while preserving every original inter-column gap exactly (a
uniform translation, not a scale, so "Boost Nav. Priority" et al keep the
identical spacing to their neighbors). The rightmost sub-block (Advanced
Options / Run Macro / Combat status, retail x=611 w=233, offset +59 from
its column) shrinks its declared width from 233 to 175 (real content only
needs ~110px) so 673+175=848 still fits the group's own 848-wide box
(contract test AuthoredShellFitsTheMinimumCanvasAndEverySizedChildFitsItsParent
enforces x+w<=parent w recursively). PROFILES_COL_SHIFT = 16px applies the
same fix to Profiles' one overlap ("Enable Auto-Navigation", real width
137px @ x=8 ends 145, over the original menu start at 140) by shifting
every Profiles column after the checkboxes (menu/CopyTo-or-New/
Clear-or-Delete/toggle/Delete) from 140/280/330/440/530 to
156/296/346/456/546 — again a pure translation preserving every original
gap. Vitals needed no shift for its widest caption ("Restam others at:",
85px @ x=8 ends 93 vs the slider at x=96, a 3px clearance) but its two
left-column elements (slider, percent label) move 4px right (96->100,
248->252) as a small safety margin since it was the tightest already-OK
case measured.
-->
<panel x="28" y="42" w="856" h="350" title="MossTank v0.1.0"
<panel x="28" y="42" w="856" h="236" title="MossTank v0.1.0"
visible="{WindowAvailable}" resize="none">
<!-- Virindi Tank tab order and compact text-strip presentation. -->
<tab x="8" y="22" w="58" h="18" text="Options"
@ -64,94 +98,66 @@
onchange="{SetFollowNavMinimumText}" maxlength="6" background="#E6000000"
tooltip="Minimum distance maintained while following or navigating." />
<button x="90" y="8" w="112" h="25" text="Force Buff" onclick="{ForceBuff}" />
<button x="90" y="38" w="112" h="25" text="Cancel Force Buff"
<button x="152" y="8" w="112" h="25" text="Force Buff" onclick="{ForceBuff}" />
<button x="152" y="38" w="112" h="25" text="Cancel Force Buff"
onclick="{CancelForceBuff}" />
<toggle x="90" y="68" w="120" h="20" text="Enable Buffing"
<toggle x="152" y="68" w="120" h="20" text="Enable Buffing"
checked="{BuffingEnabled}" onclick="{ToggleBuffing}" />
<toggle x="90" y="88" w="120" h="20" text="Enable Combat"
<toggle x="152" y="88" w="120" h="20" text="Enable Combat"
checked="{CombatEnabled}" onclick="{ToggleCombatEnabled}" />
<toggle x="90" y="108" w="120" h="20" text="Enable Navigation"
<toggle x="152" y="108" w="120" h="20" text="Enable Navigation"
checked="{NavigationEnabled}" onclick="{ToggleNavigation}" />
<toggle x="90" y="128" w="120" h="20" text="Enable Looting"
<toggle x="152" y="128" w="120" h="20" text="Enable Looting"
checked="{LootEnabled}" onclick="{ToggleLooting}" />
<toggle x="90" y="148" w="120" h="20" text="Enable Meta"
<toggle x="152" y="148" w="120" h="20" text="Enable Meta"
checked="{MetaEnabled}" onclick="{ToggleMeta}" />
<toggle x="220" y="8" w="160" h="20" text="Enable Autostack"
<toggle x="282" y="8" w="160" h="20" text="Enable Autostack"
checked="{AutoStackEnabled}" onclick="{ToggleAutoStack}" />
<toggle x="220" y="28" w="160" h="20" text="Enable Autocram"
<toggle x="282" y="28" w="160" h="20" text="Enable Autocram"
checked="{AutoCramEnabled}" onclick="{ToggleAutoCram}" />
<toggle x="220" y="48" w="120" h="20" text="Use Dispel Items"
<toggle x="282" y="48" w="120" h="20" text="Use Dispel Items"
checked="{UseDispelItemsEnabled}" onclick="{ToggleUseDispelItems}" />
<toggle x="220" y="68" w="120" h="20" text="Cast Dispel Self"
<toggle x="282" y="68" w="120" h="20" text="Cast Dispel Self"
checked="{CastDispelSelfEnabled}" onclick="{ToggleCastDispelSelf}" />
<toggle x="220" y="128" w="120" h="20" text="Auto Fellow Mgmt."
<toggle x="282" y="128" w="120" h="20" text="Auto Fellow Mgmt."
checked="{AutoFellowManagementEnabled}" onclick="{ToggleAutoFellowManagement}" />
<toggle x="220" y="148" w="160" h="20" text="M. Charges When Off"
<toggle x="282" y="148" w="160" h="20" text="M. Charges When Off"
checked="{ManaChargesWhenOffEnabled}" onclick="{ToggleManaChargesWhenOff}" />
<toggle x="386" y="8" w="160" h="20" text="Boost Nav. Priority"
<toggle x="448" y="8" w="160" h="20" text="Boost Nav. Priority"
checked="{NavigationPriorityEnabled}" onclick="{ToggleNavigationPriority}" />
<toggle x="386" y="28" w="160" h="20" text="Boost Loot Priority"
<toggle x="448" y="28" w="160" h="20" text="Boost Loot Priority"
checked="{LootPriorityBoostEnabled}" onclick="{ToggleLootPriorityBoost}" />
<toggle x="386" y="48" w="160" h="20" text="Loot Only Rare Corpses"
<toggle x="448" y="48" w="160" h="20" text="Loot Only Rare Corpses"
checked="{LootOnlyRareCorpsesEnabled}" onclick="{ToggleLootOnlyRareCorpses}" />
<toggle x="386" y="68" w="160" h="20" text="Peace Mode When Idle"
<toggle x="448" y="68" w="160" h="20" text="Peace Mode When Idle"
checked="{IdlePeaceModeEnabled}" onclick="{ToggleIdlePeaceMode}" />
<toggle x="386" y="88" w="160" h="20" text="Rebuff When Idle"
<toggle x="448" y="88" w="160" h="20" text="Rebuff When Idle"
checked="{IdleBuffTopoffEnabled}" onclick="{ToggleIdleBuffTopoff}" />
<toggle x="386" y="108" w="160" h="20" text="Fastcast Buffs"
<toggle x="448" y="108" w="160" h="20" text="Fastcast Buffs"
checked="{FastCastBuffsEnabled}" onclick="{ToggleFastCastBuffs}" />
<toggle x="386" y="128" w="160" h="20" text="Don't Shoot at Walls"
<toggle x="448" y="128" w="160" h="20" text="Don't Shoot at Walls"
checked="{DontShootAtWallsEnabled}" onclick="{ToggleDontShootAtWalls}" />
<toggle x="386" y="148" w="160" h="20" text="Fallback Debuffs if Blocked"
<toggle x="448" y="148" w="160" h="20" text="Fallback Debuffs if Blocked"
checked="{DebuffFallbackEnabled}" onclick="{ToggleDebuffFallback}" />
<toggle x="552" y="8" w="160" h="20" text="Summon Pets"
<toggle x="614" y="8" w="160" h="20" text="Summon Pets"
checked="{SummonPetsEnabled}" onclick="{ToggleSummonPets}" />
<toggle x="552" y="28" w="120" h="20" text="Custom Pet Range:"
<toggle x="614" y="28" w="120" h="20" text="Custom Pet Range:"
checked="{CustomPetRangeEnabled}" onclick="{TogglePetRangeMode}" />
<field x="672" y="28" w="40" h="16" text="{PetCustomRangeValueText}"
<field x="734" y="28" w="40" h="16" text="{PetCustomRangeValueText}"
onchange="{SetPetCustomRangeText}" maxlength="6" background="#E6000000"
tooltip="Custom pet summon range in meters." />
<label x="552" y="48" w="125" h="16" text="Pet Min. Monsters:" color="#FFE8DEC3" />
<field x="672" y="48" w="40" h="16" text="{PetDensityValueText}"
<label x="614" y="48" w="125" h="16" text="Pet Min. Monsters:" color="#FFE8DEC3" />
<field x="734" y="48" w="40" h="16" text="{PetDensityValueText}"
onchange="{SetPetDensityText}" maxlength="3" background="#E6000000"
tooltip="Minimum nearby monsters required before summoning a pet." />
<toggle x="611" y="128" w="233" h="20" text="Advanced Options"
<toggle x="673" y="128" w="175" h="20" text="Advanced Options"
checked="{AdvancedOptionsVisible}" onclick="{ToggleAdvancedOptionsVisible}" />
<toggle x="611" y="148" w="233" h="20" text="Run Macro"
<toggle x="673" y="148" w="175" h="20" text="Run Macro"
checked="{CombatMacroRunning}" onclick="{ToggleCombat}" />
<label x="611" y="170" w="233" h="16" text="{CombatStatus}" color="#FFC7B98F" />
</group>
<!-- Advanced Options popup: VTank's own AdvancedOptionsView is a
separate 392x300 OS-level window (docs/research/vtank-kb/08-ui-views.md
§1's secondary-view table); this stays in-panel (KB §5) but now uses
that exact footprint instead of the full 848x194 tab canvas, so
nothing is clipped when it opens. Controls repositioned to fit —
VTank's own popup has no Apply/Back buttons at all (edits are
destructive-live in retail); acdream keeps its own explicit
Apply/Back pair since our draft-then-submit editing flow needs them. -->
<group x="8" y="42" w="392" h="300" visible="{AdvancedOptionsVisible}">
<label x="4" y="4" w="384" h="16"
text="Advanced Options — complete VTank settings table"
color="#FFE8DEC3" />
<list x="4" y="22" w="260" h="160" rowheight="17"
items="{AdvancedOptionNames}" selected="{SelectedAdvancedOptionIndex}"
onchange="{SelectAdvancedOption}"
tooltip="Select one of Virindi Tank's 137 advanced options." />
<label x="272" y="22" w="116" h="16" text="{AdvancedOptionName}" color="#FFE8DEC3" />
<button x="272" y="42" w="116" h="23" text="Apply"
onclick="{ApplyAdvancedOption}" />
<button x="272" y="70" w="116" h="23" text="Back"
onclick="{HideAdvancedOptions}" />
<field x="4" y="188" w="260" h="23" text="{AdvancedOptionValueDraft}"
onchange="{SetAdvancedOptionValueDraft}" onsubmit="{SubmitAdvancedOption}"
maxlength="160" clearonsubmit="false" background="#E6000000"
tooltip="Edit the selected advanced-option value and press Enter or Apply." />
<label x="4" y="216" w="384" h="16" text="{AdvancedOptionNotice}" color="#FFC7B98F" />
<label x="673" y="170" w="175" h="16" text="{CombatStatus}" color="#FFC7B98F" />
</group>
<!-- Profiles: transcribed control-for-control from VTank's own Profiles
@ -163,84 +169,79 @@
meta-debug windows to close — so that slot is repurposed for our own
per-type Delete (slice 1), keeping VTank's row rhythm undisturbed;
Delete for Settings/Nav sits just past "Mine only"/past the row's
last VTank control instead. The name-draft field each CopyTo/New
needs a target for (VTank's own combos are presumably editable
in-place; acdream's <menu> is not) lives in its own row below the
four VTank rows, alongside the acdream-only loot-engine selector
(KB §5: no VTank equivalent) and the profile/loot status notices. -->
last VTank control instead.
Fix round A (2026-09-07, owner: legacy leftovers deleted): the
stacked ProfileNameDraft/RouteProfileNameDraft/MetaProfileNameDraft
field+"New"-button trio below the four VTank rows (three 20-tall
fields at zero vertical gap rendered as one solid black bar behind
three stacked "New" buttons — the owner's screenshot) is REMOVED;
Meta's naming already has a live field+button pair on the Meta tab
itself (mosstank.xml's Meta group), and Macro/Nav lose their only
in-UI CopyTo target-name entry as a result (a real, accepted
regression — see the commit body). The acdream-only "Loot engine"
classifier row (LootClassifierNames/SelectedLootClassifier, no VTank
equivalent per KB §5) and the ProfileLifecycleNotice label (the raw
macro-profile recovery path the owner saw rendered) are also
REMOVED — VTank's own Profiles tab has no notice/status control at
all. LootStatus is kept (general loot-load feedback, not itself the
reported leftover) but repositioned into the space this freed.
PROFILES_COL_SHIFT = 16px moves every column after the leftmost
checkbox/label (menu 140->156, CopyTo/New 280->296,
Clear/Delete-as-button 330->346, Mine-only/Show-Editor 440->456,
per-family Delete 530->546) — see the panel-level comment for why
(fixes "Enable Auto-Navigation" running into its dropdown). Every
gap between these columns is preserved exactly (a translation, not a
scale). -->
<group x="8" y="42" w="848" h="194" visible="{ProfilesVisible}">
<label x="8" y="8" w="142" h="16" text="Macro Settings" color="#FFE8DEC3" />
<menu x="140" y="8" w="140" h="20" items="{MacroProfileNames}"
<menu x="156" y="8" w="140" h="20" items="{MacroProfileNames}"
selected="{SelectedMacroProfile}" onchange="{SelectMacroProfile}"
rows="7" scroll="true" tooltip="Select the active macro profile." />
<button x="280" y="8" w="40" h="20" text="CopyTo" onclick="{CopyProfile}" />
<button x="330" y="8" w="100" h="20" text="Clear profile!"
<button x="296" y="8" w="40" h="20" text="CopyTo" onclick="{CopyProfile}" />
<button x="346" y="8" w="100" h="20" text="Clear profile!"
onclick="{ClearProfile}" />
<toggle x="440" y="8" w="80" h="20" text="Mine only"
<toggle x="456" y="8" w="80" h="20" text="Mine only"
checked="{MineOnlyEnabled}" onclick="{ToggleMineOnly}" />
<button x="530" y="8" w="76" h="20" text="Delete"
<button x="546" y="8" w="76" h="20" text="Delete"
onclick="{DeleteProfile}"
tooltip="Delete the selected named profile's real .usd file. Not available for By char." />
<toggle x="8" y="32" w="142" h="20" text="Enable Auto-Navigation"
checked="{NavigationEnabled}" onclick="{ToggleNavigation}" />
<menu x="140" y="32" w="140" h="20" items="{RouteProfileNames}"
<menu x="156" y="32" w="140" h="20" items="{RouteProfileNames}"
selected="{SelectedRouteProfile}" onchange="{SelectRouteProfile}"
rows="7" scroll="true" tooltip="Select the active navigation profile." />
<button x="280" y="32" w="40" h="20" text="CopyTo" onclick="{CopyRouteProfile}" />
<button x="330" y="32" w="100" h="20" text="Clear nav profile!"
<button x="296" y="32" w="40" h="20" text="CopyTo" onclick="{CopyRouteProfile}" />
<button x="346" y="32" w="100" h="20" text="Clear nav profile!"
onclick="{ClearRouteProfile}" />
<button x="440" y="32" w="76" h="20" text="Delete"
<button x="456" y="32" w="76" h="20" text="Delete"
onclick="{DeleteRouteProfile}"
tooltip="Delete the selected named route's real .af file. Not available for By char." />
<toggle x="8" y="56" w="142" h="20" text="Enable Looting"
checked="{LootEnabled}" onclick="{ToggleLooting}" />
<menu x="140" y="56" w="140" h="20" items="{LootProfileNames}"
<menu x="156" y="56" w="140" h="20" items="{LootProfileNames}"
selected="{LootProfileName}" onchange="{SelectLootProfile}"
rows="7" scroll="true" tooltip="Select the active loot profile." />
<button x="280" y="56" w="40" h="20" text="New" onclick="{CreateLootProfile}" />
<button x="330" y="56" w="100" h="20" text="Delete"
<button x="296" y="56" w="40" h="20" text="New" onclick="{CreateLootProfile}" />
<button x="346" y="56" w="100" h="20" text="Delete"
onclick="{DeleteLootProfile}"
tooltip="Delete the selected named loot profile's real .utl file. Not available for By char." />
<toggle x="440" y="56" w="80" h="20" text="Show Editor"
<toggle x="456" y="56" w="80" h="20" text="Show Editor"
checked="{LootEditorVisible}" onclick="{ToggleLootEditorVisible}" />
<toggle x="8" y="80" w="142" h="20" text="Enable Meta Actions"
checked="{MetaEnabled}" onclick="{ToggleMeta}" />
<menu x="140" y="80" w="140" h="20" items="{MetaProfileNames}"
<menu x="156" y="80" w="140" h="20" items="{MetaProfileNames}"
selected="{SelectedMetaProfile}" onchange="{SelectMetaProfile}"
rows="7" scroll="true" tooltip="Select the active meta profile." />
<button x="280" y="80" w="40" h="20" text="CopyTo" onclick="{CopyMetaProfile}" />
<button x="330" y="80" w="100" h="20" text="Delete"
<button x="296" y="80" w="40" h="20" text="CopyTo" onclick="{CopyMetaProfile}" />
<button x="346" y="80" w="100" h="20" text="Delete"
onclick="{DeleteMetaProfile}"
tooltip="Delete the selected named Meta profile's real .af file. Not available for By char." />
<field x="8" y="104" w="160" h="20" text="{ProfileNameDraft}"
onchange="{SetProfileNameDraft}" onsubmit="{CreateNamedProfile}"
maxlength="64" clearonsubmit="false" background="#E6000000"
color="#FFE8DEC3" tooltip="Name a new or copied macro profile." />
<button x="176" y="104" w="56" h="20" text="New" onclick="{CreateProfile}" />
<field x="8" y="124" w="160" h="20" text="{RouteProfileNameDraft}"
onchange="{SetRouteProfileNameDraft}"
onsubmit="{CreateNamedRouteProfile}" maxlength="64"
clearonsubmit="false" background="#E6000000" color="#FFE8DEC3"
tooltip="Name a new or copied navigation profile." />
<button x="176" y="124" w="56" h="20" text="New" onclick="{CreateRouteProfile}" />
<field x="8" y="144" w="160" h="20" text="{MetaProfileNameDraft}"
onchange="{SetMetaProfileNameDraft}"
onsubmit="{CreateNamedMetaProfile}" maxlength="64"
clearonsubmit="false" background="#E6000000" color="#FFE8DEC3"
tooltip="Name a new or copied meta profile." />
<button x="176" y="144" w="56" h="20" text="New" onclick="{CreateMetaProfile}" />
<label x="8" y="166" w="70" h="16" text="Loot engine" color="#FFC7B98F" />
<menu x="82" y="164" w="180" h="20" items="{LootClassifierNames}"
selected="{SelectedLootClassifier}" onchange="{SelectLootClassifier}"
rows="7" scroll="true" tooltip="Select the loot-rule engine for this profile." />
<label x="270" y="166" w="290" h="16" text="{ProfileLifecycleNotice}"
color="#FFC7B98F" />
<label x="566" y="166" w="180" h="16" text="{LootStatus}" color="#FF9B9072" />
<label x="8" y="108" w="400" h="16" text="{LootStatus}" color="#FF9B9072" />
</group>
<!-- Vitals: transcribed control-for-control from VTank's own Vitals tab
@ -253,52 +254,52 @@
the right of that, in space VTank's own layout never uses. -->
<group x="8" y="42" w="848" h="194" visible="{VitalsVisible}">
<label x="8" y="0" w="80" h="16" text="Heal at:" color="#FFE8DEC3" />
<slider x="96" y="0" w="144" h="16" min="0" max="100"
<slider x="100" y="0" w="144" h="16" min="0" max="100"
value="{NormalHealthPercent}" onchange="{SetNormalHealthPercent}"
tooltip="Combat health recharge threshold." />
<label x="248" y="0" w="80" h="16" text="{NormalHealthText}" color="#FFC7B98F" />
<label x="252" y="0" w="80" h="16" text="{NormalHealthText}" color="#FFC7B98F" />
<label x="8" y="16" w="80" h="16" text="Restam at:" color="#FFE8DEC3" />
<slider x="96" y="16" w="144" h="16" min="0" max="100"
<slider x="100" y="16" w="144" h="16" min="0" max="100"
value="{NormalStaminaPercent}" onchange="{SetNormalStaminaPercent}"
tooltip="Combat stamina recharge threshold." />
<label x="248" y="16" w="80" h="16" text="{NormalStaminaText}" color="#FFC7B98F" />
<label x="252" y="16" w="80" h="16" text="{NormalStaminaText}" color="#FFC7B98F" />
<label x="8" y="32" w="80" h="16" text="Get Mana at:" color="#FFE8DEC3" />
<slider x="96" y="32" w="144" h="16" min="0" max="100"
<slider x="100" y="32" w="144" h="16" min="0" max="100"
value="{NormalManaPercent}" onchange="{SetNormalManaPercent}"
tooltip="Combat mana recharge threshold." />
<label x="248" y="32" w="80" h="16" text="{NormalManaText}" color="#FFC7B98F" />
<label x="252" y="32" w="80" h="16" text="{NormalManaText}" color="#FFC7B98F" />
<label x="8" y="56" w="80" h="16" text="Top-off HP:" color="#FFE8DEC3" />
<slider x="96" y="56" w="144" h="16" min="0" max="100"
<slider x="100" y="56" w="144" h="16" min="0" max="100"
value="{NoTargetHealthPercent}" onchange="{SetNoTargetHealthPercent}"
tooltip="Idle health top-off threshold." />
<label x="248" y="56" w="80" h="16" text="{NoTargetHealthText}" color="#FFC7B98F" />
<label x="252" y="56" w="80" h="16" text="{NoTargetHealthText}" color="#FFC7B98F" />
<label x="8" y="72" w="80" h="16" text="Top-off Stam:" color="#FFE8DEC3" />
<slider x="96" y="72" w="144" h="16" min="0" max="100"
<slider x="100" y="72" w="144" h="16" min="0" max="100"
value="{NoTargetStaminaPercent}" onchange="{SetNoTargetStaminaPercent}"
tooltip="Idle stamina top-off threshold." />
<label x="248" y="72" w="80" h="16" text="{NoTargetStaminaText}" color="#FFC7B98F" />
<label x="252" y="72" w="80" h="16" text="{NoTargetStaminaText}" color="#FFC7B98F" />
<label x="8" y="88" w="80" h="16" text="Top-off Mana:" color="#FFE8DEC3" />
<slider x="96" y="88" w="144" h="16" min="0" max="100"
<slider x="100" y="88" w="144" h="16" min="0" max="100"
value="{NoTargetManaPercent}" onchange="{SetNoTargetManaPercent}"
tooltip="Idle mana top-off threshold." />
<label x="248" y="88" w="80" h="16" text="{NoTargetManaText}" color="#FFC7B98F" />
<label x="252" y="88" w="80" h="16" text="{NoTargetManaText}" color="#FFC7B98F" />
<label x="8" y="112" w="80" h="16" text="Heal others at:" color="#FFE8DEC3" />
<slider x="96" y="112" w="144" h="16" min="0" max="100"
<slider x="100" y="112" w="144" h="16" min="0" max="100"
value="{HelperHealthPercent}" onchange="{SetHelperHealthPercent}"
tooltip="Fellowship healing threshold." />
<label x="248" y="112" w="80" h="16" text="{HelperHealthText}" color="#FFC7B98F" />
<label x="252" y="112" w="80" h="16" text="{HelperHealthText}" color="#FFC7B98F" />
<label x="8" y="128" w="80" h="16" text="Restam others at:" color="#FFE8DEC3" />
<slider x="96" y="128" w="144" h="16" min="0" max="100"
<slider x="100" y="128" w="144" h="16" min="0" max="100"
value="{HelperStaminaPercent}" onchange="{SetHelperStaminaPercent}"
tooltip="Fellowship stamina-restoration threshold." />
<label x="248" y="128" w="80" h="16" text="{HelperStaminaText}" color="#FFC7B98F" />
<label x="252" y="128" w="80" h="16" text="{HelperStaminaText}" color="#FFC7B98F" />
<label x="8" y="144" w="80" h="16" text="Infuse others at:" color="#FFE8DEC3" />
<slider x="96" y="144" w="144" h="16" min="0" max="100"
<slider x="100" y="144" w="144" h="16" min="0" max="100"
value="{HelperManaPercent}" onchange="{SetHelperManaPercent}"
tooltip="Fellowship mana-infusion threshold." />
<label x="248" y="144" w="80" h="16" text="{HelperManaText}" color="#FFC7B98F" />
<label x="252" y="144" w="80" h="16" text="{HelperManaText}" color="#FFC7B98F" />
<toggle x="360" y="4" w="190" h="20" text="Enable vital recharge"
checked="{VitalUpkeepEnabled}" onclick="{ToggleVitalUpkeep}" />
@ -503,9 +504,9 @@
onclick="{AddRouteJump}" />
<button x="556" y="126" w="72" h="21" text="Remove"
onclick="{RemoveRouteWaypoint}" />
<button x="636" y="126" w="28" h="21" text="↑"
<button x="636" y="126" w="28" h="21" icon="0x060028FC" iconkind="did"
onclick="{MoveRouteWaypointUp}" tooltip="Move the selected waypoint up." />
<button x="670" y="126" w="28" h="21" text="↓"
<button x="670" y="126" w="28" h="21" icon="0x060028FD" iconkind="did"
onclick="{MoveRouteWaypointDown}" tooltip="Move the selected waypoint down." />
<menu x="4" y="148" w="90" h="22" items="{RouteModeNames}"
@ -603,92 +604,12 @@
onclick="{AddMetaRule}" />
<button x="414" y="151" w="62" h="21" text="Remove"
onclick="{RemoveMetaRule}" />
<button x="482" y="151" w="28" h="21" text="↑"
<button x="482" y="151" w="28" h="21" icon="0x060028FC" iconkind="did"
onclick="{MoveMetaRuleUp}" tooltip="Move the selected meta rule up." />
<button x="516" y="151" w="28" h="21" text="↓"
<button x="516" y="151" w="28" h="21" icon="0x060028FD" iconkind="did"
onclick="{MoveMetaRuleDown}" tooltip="Move the selected meta rule down." />
<label x="558" y="156" text="{MetaStatus}" color="#FFC7B98F" />
<label x="4" y="178" text="{MetaNotice}" color="#FF9B9072" />
</group>
<!-- Loot Editor popup: VTank's own FriendlyLootEditorView is a separate
268x300 OS-level window (docs/research/vtank-kb/08-ui-views.md §1's
secondary-view table) whose retail "Rules" page is a literal stub
("This page not yet completed") — our own editor is genuinely more
complete (KB §5). Stays in-panel (KB §5) but now uses VTank's exact
268x300 footprint instead of the full 848x194 tab canvas. Every
control from before is repositioned to fit that narrower footprint;
LootEditorNotice (a status label, not an action) is the one control
dropped to make everything else fit without clipping — a deliberate,
narrow trim given how much richer this editor is than the 268-wide
popup VTank itself budgets for. -->
<group x="8" y="42" w="268" h="300" visible="{LootEditorVisible}">
<label x="4" y="2" w="190" h="16" text="Loot profile rules (first match wins)"
color="#FFE8DEC3" />
<button x="198" y="2" w="64" h="18" text="Back"
onclick="{CloseLootEditor}" />
<field x="4" y="22" w="260" h="18" text="{LootProfileNameDraft}"
onchange="{SetLootProfileNameDraft}"
onsubmit="{CreateNamedLootProfile}" maxlength="64"
background="#E6000000" color="#FFE8DEC3"
tooltip="Name a new or copied loot profile." />
<button x="4" y="44" w="58" h="18" text="New"
onclick="{CreateLootProfile}" />
<button x="66" y="44" w="58" h="18" text="CopyTo"
onclick="{CopyLootProfile}" />
<button x="128" y="44" w="58" h="18" text="Clear"
onclick="{ClearLootProfile}" />
<button x="190" y="44" w="70" h="18" text="Delete"
onclick="{DeleteLootProfile}"
tooltip="Delete the selected named loot profile's real .utl file. Not available for By char." />
<list x="4" y="66" w="260" h="68" rowheight="17"
items="{LootRuleRows}" selected="{SelectedLootRuleIndex}"
onchange="{SelectLootRule}" tooltip="Select an ordered loot rule to edit." />
<menu x="4" y="138" w="260" h="18" items="{LootActionNames}"
selected="{SelectedLootAction}" onchange="{SelectLootAction}"
rows="9" scroll="true" tooltip="Choose the action for matching loot." />
<label x="4" y="160" w="140" h="16" text="{LootPriorityText}" color="#FFC7B98F" />
<button x="148" y="160" w="22" h="18" text="-"
onclick="{LootPriorityDown}" tooltip="Decrease loot rule priority." />
<button x="174" y="160" w="22" h="18" text="+"
onclick="{LootPriorityUp}" tooltip="Increase loot rule priority." />
<label x="4" y="180" w="140" h="16" text="{LootKeepCountText}" color="#FFC7B98F" />
<button x="148" y="180" w="22" h="18" text="-"
onclick="{LootKeepCountDown}" tooltip="Decrease the KeepUpTo count." />
<button x="174" y="180" w="22" h="18" text="+"
onclick="{LootKeepCountUp}" tooltip="Increase the KeepUpTo count." />
<field x="4" y="200" w="190" h="18" text="{LootExpressionDraft}"
onchange="{SetLootExpressionDraft}" onsubmit="{ApplyLootExpression}"
maxlength="256" background="#E6000000" color="#FFE8DEC3"
tooltip="VTClassic or UtilityBelt-compatible loot match expression." />
<button x="198" y="200" w="64" h="18" text="Apply"
onclick="{ApplyLootRule}" />
<button x="4" y="222" w="54" h="18" text="Add"
onclick="{AddLootRule}" />
<button x="60" y="222" w="64" h="18" text="Remove"
onclick="{RemoveLootRule}" />
<button x="128" y="222" w="26" h="18" text="↑"
onclick="{MoveLootRuleUp}" tooltip="Move the selected loot rule up." />
<button x="158" y="222" w="26" h="18" text="↓"
onclick="{MoveLootRuleDown}" tooltip="Move the selected loot rule down." />
<toggle x="4" y="244" w="128" h="18" text="Loot All Corpses"
checked="{LootAllCorpsesEnabled}" onclick="{ToggleLootAllCorpses}" />
<toggle x="136" y="244" w="128" h="18" text="Loot Fellow Corpses"
checked="{LootFellowCorpsesEnabled}"
onclick="{ToggleLootFellowCorpses}" />
<toggle x="4" y="264" w="128" h="18" text="Loot Only Rare Corpses"
checked="{LootOnlyRareCorpsesEnabled}"
onclick="{ToggleLootOnlyRareCorpses}" />
<toggle x="136" y="264" w="128" h="18" text="Read Unknown Scrolls"
checked="{ReadUnknownScrollsEnabled}"
onclick="{ToggleReadUnknownScrolls}" />
<label x="4" y="282" w="140" h="16" text="{LootRangeText}" color="#FFC7B98F" />
<button x="148" y="282" w="22" h="16" text="-"
onclick="{LootRangeDown}" tooltip="Decrease corpse-looting range." />
<button x="174" y="282" w="22" h="16" text="+"
onclick="{LootRangeUp}" tooltip="Increase corpse-looting range." />
</group>
</panel>

View file

@ -23,6 +23,12 @@
<None Include="..\..\src\AcDream.Plugins.MossTank\mosstank.xml"
Link="mosstank.xml"
CopyToOutputDirectory="PreserveNewest" />
<None Include="..\..\src\AcDream.Plugins.MossTank\mosstank-advanced.xml"
Link="mosstank-advanced.xml"
CopyToOutputDirectory="PreserveNewest" />
<None Include="..\..\src\AcDream.Plugins.MossTank\mosstank-loot-editor.xml"
Link="mosstank-loot-editor.xml"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<None Include="Fixtures\vtank\**\*.*" CopyToOutputDirectory="PreserveNewest" />

View file

@ -187,7 +187,14 @@ public sealed class MossTankMarkupContractTests
// buttons + 3 menus + 14 toggles + 3 equip buttons = 30 controls)
// is gone, replaced by 1 list + 23 columns + 1 field + 2 buttons
// (27) — net 205 -> 202.
Assert.Equal(202, controls.Length);
// Fix round A (2026-09-07) moved the Advanced Options and Loot
// Editor groups OUT of mosstank.xml entirely into their own plugin
// panels (mosstank-advanced.xml / mosstank-loot-editor.xml, this
// contract only scans mosstank.xml) and deleted the Profiles tab's
// three stacked name-draft field+"New"-button rows and the
// acdream-only "Loot engine" classifier menu (owner: legacy
// leftovers) — net 202 -> 167.
Assert.Equal(167, controls.Length);
foreach (XElement control in controls)
{
@ -274,15 +281,95 @@ public sealed class MossTankMarkupContractTests
// Campaign VT slice 7 S7.1: 856 wide, matching VTank's own
// mainView.xml content (856x210, docs/research/vtank-kb/
// 08-ui-views.md §0). 350 tall (not 210) only because the
// Advanced Options / Loot Editor popups moved to VTank's own
// 392x300 / 268x300 popup geometry as separate in-panel groups —
// see mosstank.xml's own panel-level comment.
// 08-ui-views.md §0). Fix round A (2026-09-07) moved the Advanced
// Options / Loot Editor popups OUT into their own plugin panels
// (mosstank-advanced.xml / mosstank-loot-editor.xml), so the main
// window is back to VTank's own proportion: 236 tall (42px header +
// tab strip, unchanged since S7.1, + 194px per-tab content,
// unchanged) with no dead space below the tabs — see mosstank.xml's
// own panel-level comment.
Assert.Equal(856f, Number(root, "w"));
Assert.Equal(350f, Number(root, "h"));
Assert.Equal(236f, Number(root, "h"));
AssertWithinParent(root);
}
/// <summary>
/// Fix round A (2026-09-07): the Advanced Options and Loot Editor popups
/// moved out of mosstank.xml into their own plugin panel files. This
/// pins that both files still parse, still declare VTank's own popup
/// footprint (392x300 / 268x300, docs/research/vtank-kb/08-ui-views.md
/// §1's secondary-view table), every child fits its own declared bounds,
/// and every markup binding still resolves against a real
/// <see cref="MossTankPanel"/> property — the same three guarantees
/// <see cref="VtankTabOrderAndEveryBindingResolveAgainstTheLivePanel"/>
/// and <see cref="AuthoredShellFitsTheMinimumCanvasAndEverySizedChildFitsItsParent"/>
/// give the main panel, now split across three files instead of one.
/// </summary>
[Theory]
[InlineData("mosstank-advanced.xml", 392f, 300f)]
[InlineData("mosstank-loot-editor.xml", 268f, 300f)]
public void SecondaryPopupPanelsFitTheirOwnBoundsAndEveryBindingResolves(
string fileName, float expectedWidth, float expectedHeight)
{
XDocument document = XDocument.Load(
Path.Combine(AppContext.BaseDirectory, fileName));
XElement root = Assert.IsType<XElement>(document.Root);
Assert.Equal(expectedWidth, Number(root, "w"));
Assert.Equal(expectedHeight, Number(root, "h"));
AssertWithinParent(root);
PropertyInfo[] properties = typeof(MossTankPanel).GetProperties(
BindingFlags.Instance | BindingFlags.Public);
var byName = properties.ToDictionary(
static property => property.Name,
StringComparer.Ordinal);
foreach (XAttribute attribute in root.DescendantsAndSelf().Attributes())
{
string value = attribute.Value;
if (!value.Contains('{', StringComparison.Ordinal))
continue;
Assert.Matches("^\\{[^{}]+\\}$", value);
string name = value[1..^1];
Assert.True(
byName.ContainsKey(name),
$"Markup binding {value} on <{attribute.Parent?.Name}> in "
+ $"{fileName} has no public MossTankPanel property.");
}
}
/// <summary>
/// Fix round A: neither popup's own "↑"/"↓" reorder buttons (Loot
/// Editor's MoveLootRuleUp/Down) render as blank slots — the retail
/// default UI font (0x40000000) has no glyph for those Unicode
/// characters, so they now carry the same DAT move-up/move-down icons
/// (0x060028FC/0x060028FD) the Monsters grid already uses, and no
/// <c>&lt;button&gt;</c> anywhere is left with the literal arrow text.
/// </summary>
[Fact]
public void NoButtonAnywhereUsesTheUnrenderableArrowGlyphs()
{
foreach (string fileName in new[]
{
"mosstank.xml", "mosstank-advanced.xml",
"mosstank-loot-editor.xml",
})
{
XDocument document = XDocument.Load(
Path.Combine(AppContext.BaseDirectory, fileName));
XElement root = Assert.IsType<XElement>(document.Root);
foreach (XElement button in root.Descendants("button"))
{
string? text = (string?)button.Attribute("text");
Assert.False(
text is "↑" or "↓",
$"<button> in {fileName} still uses the unrenderable "
+ $"'{text}' glyph instead of a DAT icon.");
}
}
}
[Fact]
public void TextlessAndAbbreviatedControlsHaveAccessibleRetailTooltips()
{