Commit graph

7 commits

Author SHA1 Message Date
Erik
8376105936 fix(vtank): slice 7 round F item 6 — Buffs gap, Loot Editor Back, Route count column
Three small resemblance-re-check nits.

Buffs: the extras strip (toggles/Difficulty/Rebuff/Buff button, no VTank
counterpart) stayed at its pre-Round-D x=332 while Round D item 4's own
+128 right-anchor shift moved the Blacklisted Buff Families list from
x=524 to x=652 — an 8px left margin against a 138px right margin.
Discovered a hard geometric ceiling while fixing this: Extra Buff
Spells never moves (anchor="left top bottom", pinned x=4..324) but
Blacklisted Buff Families tracks the growing right edge, sitting at
x=524 at the panel's own tested FLOOR (856x236) and x=652 at the
984-wide enlarged default — a 128px swing. A single fixed-position
strip can only be centered against ONE of those widths; centering
against the 984 default (tried x=397 first) put the strip's right edge
55px inside where the list sits at the floor — a real overlap the
existing ResolvedMainPanelHasNoOverlapOrOutOfBoundsChildAtThisSize pin
(856x236 case) caught immediately. x=333 is the widest safe position
(9px margins against the floor's 324..524 gap) — 1px from where fix
round B item 2 already had it. The residual 984-default asymmetry
(~9/137, barely changed from ~8/138) is an accepted, unavoidable-
without-a-redesign limitation, the same "dead margin, polish later"
trade already accepted for six other tabs at this size. Coverage moved
from y=213 (anchored to the group's bottom edge, a real 55px empty
hole once the panel grew past its original 194-tall size) to sit
directly under the Buff button row (y=176, no anchor).

Loot Editor: dropped the leftover standalone "Back" button — the
window's own OS-level title bar already closes it, matching every
other plugin popup (Advanced Options included) since round D.
CloseLootEditor stays real, still called by ToggleLootEditorVisible.

Route: the waypoint grid's count column (declared width="30") was the
grid's LAST column, so docs/plugin-ui-markup.md's own "Width semantics"
rule made that declared width never validated/used — it silently
absorbed whatever remainder was left (33px normally, but only 17px once
the scrollbar's 16px reservation ate into the list's own 370px width,
since 337+30 already exceeded 370-16=354). A trailing filler column
alone wasn't sufficient — proved by a targeted mutation below — since
even non-last, a truly fixed 30px column still gets clamped by the
same 354px ceiling. The real text column narrows 337->324
(324+30=354, exactly matching the scrollbar-reduced width) so the
count column gets its full 30px whether or not the scrollbar shows.

Every new/changed pin shown to fail against a targeted mutation first:
LootEditorHasNoLeftoverBackButton failed with the button re-inserted,
then passed once removed. RouteShapedGrid_CountColumnStaysThirtyPxWhen
TheListScrolls (a synthetic list matching Route's real 370/324/30/*
shape, 9 rows forcing the scrollbar, reflecting into UiMarkupList's own
_cachedLayout) failed both against the original 2-column 337/30 shape
(17px, reproducing the reported squeeze) AND against filler-only-no-
narrowing (also 17px, proving the filler alone isn't the fix), then
passed at exactly 30px once both changes landed together.
BuffsExtrasStripIsCenteredAtThePanelsFloorAndCoverageSitsUnderTheButtonRow
and its App-side resolved-tree counterpart
BuffsExtrasStripStaysCenteredAndNonOverlappingAtTheFloor pin the
floor-centered position and the Coverage gap.

MossTank suite 725 -> 726 (one new pin); App markup/plugin filter
260 -> 262 (two new pins).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 19:28:30 +02:00
Erik
bae34aaa8b fix(vtank): slice 7 round F item 5 — Monsters move-up/down arrows stay adjacent
Resemblance re-check: "Monsters' move-down arrow drifted ~67 px from
move-up because the last (auto) column absorbed the new width — add a
trailing filler column." docs/plugin-ui-markup.md's own "Width
semantics" rule makes the LAST <column> in a <list> ALWAYS treated as
auto, regardless of its own declared width — MonsterMoveDownIcons was
the grid's 23rd and, until this commit, LAST column, so it silently
absorbed every pixel of window growth instead of staying at its
declared 23px pitch. The gap was already wrong (89.5px) at the
authored 984-wide default, not just after a resize.

Adds a 24th, trailing <column type="text"> with empty items
(MonsterFillerColumn) so MoveDownIcons is no longer last and keeps its
own real width; the filler absorbs the remainder instead — the same
"fixed icon columns + trailing auto filler" shape lstMetaRules' three
16px icon columns + two auto text columns already prove out
(MarkupListColumnsTests.EndToEnd_MetaShapedSixColumnList_...). The
filler's onclick (MonsterFillerClick) is a real, bound no-op —
MossTankMarkupContractTests.EveryInteractiveControlDeclaresARealHandlerBinding
requires every <column> to declare SOME handler, so "no binding at all"
(the other option this item's own spec offered) is not actually legal
in this codebase; clicking blank filler space does nothing, but the
binding itself is real.

Mutation shown to fail first: the new
MonstersMoveUpAndMoveDownIconsStayAdjacentAtEveryWidth theory (984 and
1100, MarkupDocument.Build over the real mosstank.xml with a real
IMarkupIconResolver stub) failed at BOTH widths against the unmodified
markup — 89.5px apart at 984, 147.5px at 1100 — then passed (fixed
~23px gap at both) once the filler column was added.
MonstersGridHasVtanksTwentyThreeColumnsInOrderWithRetailHeaderTooltips'
23->24 column-count/type update is the other required change; VTank's
own 23-column shape is unchanged, only a MossTank-only 24th filler is
new.

MossTank suite 724/724 (one test's expectations updated, no net count
change); App markup/plugin filter 258 -> 260 (the new theory's two
InlineData cases).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 19:04:56 +02:00
Erik
ad3f4df575 fix(vtank): slice 7 round F item 1 — Advanced Options bottom block gone
Owner's second live look (2026-09-07): "The whole bottom window of
advanced options should not be there." VTank's own AdvancedOptionsView
is a bare list + category list + edit field + description (KB
08-ui-views.md §1) — no notice line, no extra controls. Deletes the
"Editing X." notice label and the entire "MossTank Extras" section fix
round B item 8/9 added below it (Checkpoint/Jump/Remove/Set Follow
Target + status label/Follow Corners/Open Doors/Nav Priority/
Follow-Nav min distance stepper), shrinking the popup back to VTank's
392x300 (resizable, min = that; was 392x450).

Every control removed had a real path already, so nothing is actually
unreachable:
  - Checkpoint/Jump: `/mt addnavcheckpoint` / `/mt addnavjump` (already
    documented in the /vt help text).
  - Remove: click any row of the Route tab's own waypoint grid
    (DeleteRouteWaypointAt) — RemoveRouteWaypointCore and
    DeleteRouteWaypointAtCore are the identical removal logic, so the
    button duplicated a control that already exists.
  - Set Follow Target: re-select "Follow" in the Route tab's own
    nav-mode menu — SelectRouteMode already calls CaptureFollowTarget
    every time RouteMode.Target is (re-)selected.
  - Follow Corners/Open Doors/Nav Priority/Follow-Nav min distance:
    real VtankOptionCatalog entries (FollowAroundCorners, OpenDoors,
    NavPriorityBoost, NavCloseStopRange) already listed and editable in
    THIS SAME popup's own option list/field above, or via
    `/vt opt set <Name> <value>`.
No C# state or command was deleted — MossTankPanel's properties/actions
(RemoveRouteWaypoint, SetFollowTarget, AddRouteCheckpoint, AddRouteJump,
NavigationSettings.FollowAroundCorners/OpenDoors/Priority/
MinimumDistanceMeters) are untouched; only the redundant second UI
surface for them is gone.

Mutation shown to fail first: MossTankMarkupContractTests'
ExpectedPopupBounds and MossTankMarkupBuildOverRealFilesTests'
WideningTheRealAdvancedOptionsPopupGrowsTheOptionListWithoutOverlappingItsSibling
were changed to expect 392x300 before the markup shrink — both failed
with "Expected: 300 Actual: 450" against the still-450-tall file, then
passed once mosstank-advanced.xml's own h/minh dropped to 300.

MossTank suite 722/722 (no count change, no control removed here had a
dedicated test); App markup/plugin filter 258/258.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 18:41:08 +02:00
Erik
7badbee889 test(vtank): slice 7 round E item D-6 — resolved-geometry pin at minw/enlarged size
Every real-file re-layout pin so far inspects ONE named element after a
resize (the Monsters list, the Advanced Options option/category
lists). Adds a whole-tree pin at both ends of the main panel's declared
resize range — the minw/minh floor (856x236) and one enlarged size
(1100x320) past the 984x271 default — for each of the nine tabs in
turn: builds the real mosstank.xml through MarkupDocument.Build, draws
once at the authored default to capture anchor baselines, resizes, and
asserts no sibling overlap and no child crossing its parent's edge.
Adapts MossTankMarkupContractTests' AssertNoSiblingOverlap/
AssertWithinParent algorithm (rectangle intersection, ignore
touching/zero-size boxes, two bare <group> tab pages never overlap-
check each other) from the authored XElement tree to the resolved
UiElement tree (real Left/Top/Width/Height post-anchor), since a resize
only exists at that level.

Two real bugs surfaced and fixed in the test itself while building this:
(1) reusing one built tree across all nine tabs let an earlier tab's
resize leave the shared root already at the target size by the time a
later tab's own descendants took their "first" (baseline) anchor
capture — fixed by building a fresh tree per tab; (2) walking into an
INVISIBLE tab group's descendants compared their stale, never-
anchored-for-this-size geometry against the group's own (correctly
resized) bounds — fixed by skipping recursion past an invisible node,
since it never got a real anchor pass this round. A genuine false
positive from the test harness itself was also found and excluded:
UiLabel.OnDraw (the CT-GF1 fix) always overwrites Width/Height to the
current measured text extent, and this stub renderer has no real
DatFont, so it falls through to a crude "text.Length * 7f" placeholder
far wider than the authored box or the real DAT font would ever
measure — labels are excluded from the size checks (position still
checked via recursion) for the same reason D-3's own comment cites this
constraint.

Mutation shown to fail first: removing the Monsters list's
anchor="left right top bottom" attribute (temporary edit, reverted —
net diff on mosstank.xml is zero) reproduced "UiMarkupList @
(0,16,976,151) crosses the right edge of UiPanel (w=848)" at the 856
floor, exactly the failure this pin exists to catch.

App markup/plugin filter 244 -> 246; MossTank suite holds 722/722
(App-side test only).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 18:25:16 +02:00
Erik
64b62fea33 fix(vtank): slice 7 round E item D-3 — Advanced Options popup actually resizable
mosstank.xml's own top-of-file comment already claimed the Advanced
Options popup "gets its own resizable=\"true\" + anchored lists" — the
popup file itself still carried the dead resize="none" attribute
(MarkupDocument.cs: "resize" only ever means anything once
resizable="true" already arms the master switch), so the claim was
false and the popup was permanently fixed-size.

resizable="true" now floors minw/minh at the pre-fix authored size
(392x450 — never shrink below the tested layout, matching the main
panel's own rule). lOptionList grows WIDTH ONLY: the value-edit field,
description, and notice all sit directly below it with zero vertical
slack and none of them anchor to shift down in lockstep, so any height
growth here would walk the list straight into the field below — the
same "grow only in the direction with nothing in the way" principle the
main panel already applies to Items/Consumables/Buffs/Route's lists.
lFilterList (the category checklist beside it) tracks the growing right
edge via its own anchor="right" so the widening option list never walks
into it, matching the main panel's own right-pinned-sibling pattern.
AdvancedOptionDescription is declared anchor="left top right" for
markup consistency, but documented as a real no-op: UiLabel.OnDraw
(UiPanel.cs) always overwrites Width/Height to the current measured
text extent on every draw, right after the anchor pass runs in the same
frame, so nothing a label's anchor computes is ever visible — a
genuinely working vertical anchor was deliberately avoided since the
notice label directly below isn't anchored either and a large resize
would walk description into it; fully anchoring the rest of the
vertical stack is a larger redesign left for a future round.

Mutation shown to fail first: reverting the markup (git checkout, patch
saved and reapplied) failed at Assert.True(built.Resizable) — the popup
was still fixed-size.

App markup/plugin filter 243 -> 244; MossTank suite holds 721/721
(this item touches App-side markup only).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 18:13:06 +02:00
Erik
2d626971d9 fix(vtank): slice 7 round D item 4 — resizable panel, larger default
Owner live report 2026-09-07: "The size of the entire window needs to
be enlarged for default and should also be resizeable." mosstank.xml's
main panel is now resizable="true" with minw/minh floored at the
PRE-round-D authored size (856x236) and a new default authored size
~15% larger in both directions (984x271) — every one of the nine tab
groups grows by the identical 128px-wide/35px-tall delta (848x194 ->
976x229), preserving the original 0px group-to-panel margin design
exactly.

Every tab <group> gets anchor="left top right bottom" so a future
manual drag-resize keeps growing it. The two single-list tabs with
nothing beside their list (Monsters, Meta) give their list the SAME
full stretch, widened AND heightened by the same 128x35 delta at
author time — anchors only react to CHANGE from their first-draw
baseline, so bumping only the outer group while leaving the list's own
geometry untouched would leave it visually unchanged at the new
default (the growth invisibly reserved as margin until a further
manual resize). Every other tab's list(s) grow in HEIGHT only (+35),
pinned to whichever horizontal edge doesn't have a sibling list/button
column in the way (Items/Consumables/Buffs/Route all have one) — a
right-pinned sibling (Consumables' Excluded Scarab list, Buffs'
Blacklisted Buff Families list) is repositioned +128 at author time so
it already sits flush against the enlarged default's right edge.
Trailing "Add" rows/status labels below a taller list shift down by
the same +35 and get anchor="bottom" to keep tracking. Options/
Profiles/Vitals need no internal changes — only their own group grows.

Deviations, both documented in mosstank.xml's own Round D item 4
comment: Monsters' extra 128px of list width lands in its LAST column
(the MoveDown icon, which always absorbs the remainder per the markup
grammar) — a wider icon cell, not a redesigned grid; seven of nine
tabs' groups grow wider than their content uses, leaving harmless
empty space on the right at the enlarged default rather than
redesigning nine tabs' pixel geometry in one pass (a deliberate,
minimal-risk choice — nothing moved INTO another control's space, only
into previously-empty margin, so every existing AssertNoSiblingOverlap/
AssertWithinParent guarantee still holds).

Tests added:
- MossTankMarkupContractTests.AuthoredShellFitsTheMinimumCanvasAnd...
  updated to the new 984x271 default.
- PanelIsResizableFlooredAtThePreRoundDAuthoredSize (resizable="true",
  minw/minh = 856/236).
- EveryStretchingListDeclaresARealAnchor (contract pin: every list in
  Monsters/Items/Consumables/Buffs/Route/Meta declares a real anchor
  attribute, not the silent left-top default).
- MossTankMarkupBuildOverRealFilesTests.
  WideningTheRealMainPanelWidensTheRealMonstersList (App.Tests): builds
  the REAL mosstank.xml against a real MossTankPanel, widens the built
  root by 100px, and confirms the Monsters list's own Width grows in
  turn — the same mechanism MarkupResizableAnchorTests already proves
  against synthetic markup, now proven against the shipped file.

Mutation shown to fail: temporarily removing the Monsters list's own
anchor attribute failed the new re-layout test (976 -> 976, no growth);
restored and confirmed green. A build-copy gotcha surfaced while
writing these tests: dotnet build's default incremental copy did NOT
refresh the test projects' deployed mosstank.xml after editing the
source file (PreserveNewest apparently didn't see it as newer under
normal incremental evaluation) — dotnet build --no-incremental was
needed to get a fresh copy into bin/ before the size/anchor tests
would actually exercise the new markup instead of a stale cached copy.

Verified: dotnet build AcDream.slnx -c Release (clean, --no-incremental)
green; MossTank suite 715/715 (713 -> 715, two new markup contract
pins); App markup/plugin filter 243/243 (242 -> 243, one new re-layout
test).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 17:24:31 +02:00
Erik
7ca9785665 test(app): slice 7 fix round B item 15 — build every mosstank*.xml against a real MossTankPanel
Every existing MossTank markup pin (MossTankMarkupContractTests) validates
mosstank*.xml against MossTankPanel through reflection alone — "does a
public property with this name and this CLR type exist" — never through
MarkupDocument.Build itself, the code that actually mounts a plugin panel
at runtime. Build has its own validation a reflection-only check can't
see (attribute-format exceptions, delegate-shape mismatches, numeric-
attribute parsing, column-type dispatch). Before this test, a markup bug
of that shape would throw inside RetailUiRuntime.MountPlugins's own
try/catch and the panel would simply not appear — no test failure, no
visible error short of a live client screenshot.

New MossTankMarkupBuildOverRealFilesTests (AcDream.App.Tests) builds every
real mosstank*.xml file against a REAL MossTankPanel with a stub
IPluginHost (same shape as MossTankMarkupContractTests.StubHost) and a
fake sprite resolver. Required: a real compile-time ProjectReference from
AcDream.App.Tests to AcDream.Plugins.MossTank (test-only exception to the
plugin/host boundary — AcDream.App itself never links this assembly, see
its own ReferenceOutputAssembly=false copy target) plus
InternalsVisibleTo("AcDream.App.Tests") on the plugin project (MossTankPanel
is internal). The test project also globs mosstank*.xml from the plugin's
source directory into its own output, same pattern item 13 established.

This test caught a REAL bug on its first run: mosstank-advanced.xml's new
lFilterList (item 9's category checklist) had no selected="..." attribute.
MarkupDocument.BindRequiredIntReader throws "list selected must be an int
binding" when a <list>'s selected attribute is missing at all — a pure
checklist with no real "selected row" concept still needs one to satisfy
the markup grammar. Fixed by adding SelectedAdvancedOptionCategoryIndex
(always -1; item 10 already removed row-selection-band rendering for
every column grid, so this drives no visible highlight) and wiring
selected="{SelectedAdvancedOptionCategoryIndex}" onto that list.

tests/AcDream.Plugins.MossTank.Tests: 675/675 (unchanged — this item's
scope is entirely App-side plus one real bug fix invisible to MossTank's
own reflection-only pins).
tests/AcDream.App.Tests --filter Markup|Plugin|UiMenu|Slider: 283/3 skipped/286 (was 278/3/281, +5: the new Theory's 5 file cases).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 13:58:18 +02:00