fix(vtank): slice 7 fix round B item 2 — restore VTank's Buffs silhouette, add sibling-overlap pin
Owner's silhouette rule: VTank's own controls are never shrunk/moved/
reordered to make room for MossTank extras. The Buffs tab's Difficulty "+"
button (338,108,26,20) overlapped the Extra Buff Spells "Add..." button
(336,102,120,16) because both VTank lists had been narrowed to 246/256x84
to coexist with MossTank's toggle/difficulty/rebuff/Buff-cast-now block in
the same two columns.
Fix: restore both lists to VTank's real 320x116 at the far left (x=4) and
far right (x=524) with their own 120-wide "Add..." row below each list.
Move the MossTank-only extras block (7 toggles, Difficulty +/-, Rebuff +/-,
the Buff button+status, Coverage) into the empty 184px middle strip between
the two lists that VTank's own layout never uses. Coverage moves to y=178
(194-16) per the upcoming bottom-fit rule.
Also fixed: a second real overlap the new pin found on the Options tab —
"Pet Min. Monsters:" (614,48,125,16) overlapped its own field
(734,48,40,16) by 5px; narrowed the label to the same 120px column width
every other label/field pair in that block uses.
Added AuthoredControlsInTheSameContainerNeverOverlapASibling (4 file cases)
plus a direct predicate pin (AssertNoSiblingOverlap_CatchesARealOverlap...)
proving the helper catches a real overlap, ignores touching edges, and
ignores two <group> tab-pages sharing one rectangle (mutually exclusive via
their own visible="{...}" binding) — the existing AssertWithinParent only
ever checked a child against its OWN parent's bounds, never against a
sibling.
Mutation check: setting DifficultyUp's button to (472,112) — the same rect
as DifficultyDown — turned AuthoredControlsInTheSameContainerNeverOverlapASibling
red against the real mosstank.xml ("<button> text='-' ... overlaps sibling
<button> text='+' ..."); reverting turns it green.
tests/AcDream.Plugins.MossTank.Tests: 665/665 (was 660/660, +5: the new
theory's 4 file cases + 1 predicate fact).
tests/AcDream.App.Tests --filter Markup|Plugin|UiMenu|Slider: 276/3 skipped/279 (unchanged).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
040d5f3d81
commit
0c2c3b572d
2 changed files with 155 additions and 46 deletions
|
|
@ -307,6 +307,105 @@ public sealed class MossTankMarkupContractTests
|
|||
AssertWithinParent(root);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix round B item 2: <see cref="AssertWithinParent"/> only ever proved
|
||||
/// a control fits inside its OWN parent's declared bounds — it never
|
||||
/// caught two SIBLINGS overlapping each other while each individually
|
||||
/// still fit. That's exactly how the Buffs tab's Difficulty "+" button
|
||||
/// (338,108,26,20) ended up drawn on top of the Extra Buff Spells
|
||||
/// "Add..." button (336,102,120,16) before this fix round, and how the
|
||||
/// Options tab's "Pet Min. Monsters:" label (614,48,125,16) overlapped
|
||||
/// the very field it labels (734,48,40,16) by 5px. This walks every
|
||||
/// container in every plugin panel file and asserts no two positioned
|
||||
/// children overlap — except two <c><group></c> elements (VTank
|
||||
/// tab pages are mutually exclusive via their own <c>visible="{...}"</c>
|
||||
/// binding, so sharing the same rectangle is by design) and
|
||||
/// <c><column></c> (Campaign VT slice 1's <c><list><column></c>
|
||||
/// children have no <c>x</c>/<c>y</c> grammar at all — see
|
||||
/// docs/plugin-ui-markup.md's "Columns" — so every column reads as
|
||||
/// (0,0) and would trivially "overlap" every other column).
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("mosstank.xml")]
|
||||
[InlineData("mosstank-advanced.xml")]
|
||||
[InlineData("mosstank-loot-editor.xml")]
|
||||
[InlineData("mosstank-buffpicker.xml")]
|
||||
public void AuthoredControlsInTheSameContainerNeverOverlapASibling(string fileName)
|
||||
{
|
||||
XDocument document = XDocument.Load(
|
||||
Path.Combine(AppContext.BaseDirectory, fileName));
|
||||
XElement root = Assert.IsType<XElement>(document.Root);
|
||||
AssertNoSiblingOverlap(root);
|
||||
}
|
||||
|
||||
/// <summary>Direct pin on the overlap PREDICATE itself (independent of
|
||||
/// any real markup file): two rectangles that truly overlap must be
|
||||
/// caught, touching-but-not-overlapping edges must not be a false
|
||||
/// positive, and two sibling <group>s sharing one rectangle (the
|
||||
/// normal VTank-tab-page shape) must be ignored.</summary>
|
||||
[Fact]
|
||||
public void AssertNoSiblingOverlap_CatchesARealOverlapAndIgnoresGroupPagesAndTouchingEdges()
|
||||
{
|
||||
var overlapping = new XElement("panel",
|
||||
new XAttribute("w", "848"), new XAttribute("h", "194"),
|
||||
new XElement("button", new XAttribute("x", "336"), new XAttribute("y", "102"),
|
||||
new XAttribute("w", "120"), new XAttribute("h", "16")),
|
||||
new XElement("button", new XAttribute("x", "338"), new XAttribute("y", "108"),
|
||||
new XAttribute("w", "26"), new XAttribute("h", "20")));
|
||||
Assert.Throws<Xunit.Sdk.TrueException>(() => AssertNoSiblingOverlap(overlapping));
|
||||
|
||||
var touchingEdges = new XElement("panel",
|
||||
new XAttribute("w", "848"), new XAttribute("h", "194"),
|
||||
new XElement("button", new XAttribute("x", "0"), new XAttribute("y", "0"),
|
||||
new XAttribute("w", "100"), new XAttribute("h", "20")),
|
||||
new XElement("button", new XAttribute("x", "100"), new XAttribute("y", "0"),
|
||||
new XAttribute("w", "100"), new XAttribute("h", "20")));
|
||||
AssertNoSiblingOverlap(touchingEdges); // must not throw
|
||||
|
||||
var twoGroupPages = new XElement("panel",
|
||||
new XAttribute("w", "848"), new XAttribute("h", "236"),
|
||||
new XElement("group", new XAttribute("x", "8"), new XAttribute("y", "42"),
|
||||
new XAttribute("w", "848"), new XAttribute("h", "194")),
|
||||
new XElement("group", new XAttribute("x", "8"), new XAttribute("y", "42"),
|
||||
new XAttribute("w", "848"), new XAttribute("h", "194")));
|
||||
AssertNoSiblingOverlap(twoGroupPages); // must not throw
|
||||
}
|
||||
|
||||
private static void AssertNoSiblingOverlap(XElement container)
|
||||
{
|
||||
XElement[] children = container.Elements()
|
||||
.Where(static child => child.Name.LocalName != "column")
|
||||
.ToArray();
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
for (int j = i + 1; j < children.Length; j++)
|
||||
{
|
||||
XElement a = children[i], b = children[j];
|
||||
if (a.Name.LocalName == "group" && b.Name.LocalName == "group")
|
||||
continue;
|
||||
Assert.True(
|
||||
!RectanglesOverlap(a, b),
|
||||
$"<{a.Name}> text='{(string?)a.Attribute("text")}' @ "
|
||||
+ $"({Number(a, "x")},{Number(a, "y")},{Number(a, "w")},{Number(a, "h")}) "
|
||||
+ $"overlaps sibling <{b.Name}> text='{(string?)b.Attribute("text")}' @ "
|
||||
+ $"({Number(b, "x")},{Number(b, "y")},{Number(b, "w")},{Number(b, "h")}).");
|
||||
}
|
||||
}
|
||||
foreach (XElement child in children)
|
||||
AssertNoSiblingOverlap(child);
|
||||
}
|
||||
|
||||
private static bool RectanglesOverlap(XElement a, XElement b)
|
||||
{
|
||||
float aw = Number(a, "w"), ah = Number(a, "h");
|
||||
float bw = Number(b, "w"), bh = Number(b, "h");
|
||||
if (aw <= 0f || ah <= 0f || bw <= 0f || bh <= 0f)
|
||||
return false; // an element with no declared size never "occupies" space
|
||||
float ax = Number(a, "x"), ay = Number(a, "y");
|
||||
float bx = Number(b, "x"), by = Number(b, "y");
|
||||
return ax < bx + bw && bx < ax + aw && ay < by + bh && by < ay + ah;
|
||||
}
|
||||
|
||||
/// <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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue