diff --git a/src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj b/src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj
index 957c01406..1bb753011 100644
--- a/src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj
+++ b/src/AcDream.Plugins.MossTank/AcDream.Plugins.MossTank.csproj
@@ -11,6 +11,14 @@
+
+
diff --git a/src/AcDream.Plugins.MossTank/MossTankPanel.cs b/src/AcDream.Plugins.MossTank/MossTankPanel.cs
index 11f67c764..1da42204f 100644
--- a/src/AcDream.Plugins.MossTank/MossTankPanel.cs
+++ b/src/AcDream.Plugins.MossTank/MossTankPanel.cs
@@ -471,6 +471,14 @@ internal sealed partial class MossTankPanel
public IReadOnlyList AdvancedOptionCategoryNames { get; } =
VtankOptionCatalog.CategoryBits.Select(bit => $"0x{bit:X}").ToArray();
public IReadOnlyList AdvancedOptionCategoryEnabled => _advancedOptionCategoryEnabled;
+ // Fix round B item 15's own build-over-real-files test caught this:
+ // markup REQUIRES a "selected" int binding (MarkupDocument.
+ // BindRequiredIntReader throws "must be an int binding" when the
+ // attribute is missing) even for a pure checklist with no real
+ // selection concept — item 10 already removed the row-selection band
+ // rendering for every column grid, so this value drives no visible
+ // highlight; it exists purely to satisfy the markup grammar.
+ public int SelectedAdvancedOptionCategoryIndex => -1;
public Action ToggleAdvancedOptionCategoryAt => index =>
{
if ((uint)index >= (uint)_advancedOptionCategoryEnabled.Length)
diff --git a/src/AcDream.Plugins.MossTank/mosstank-advanced.xml b/src/AcDream.Plugins.MossTank/mosstank-advanced.xml
index 91dab2938..a99cf30fd 100644
--- a/src/AcDream.Plugins.MossTank/mosstank-advanced.xml
+++ b/src/AcDream.Plugins.MossTank/mosstank-advanced.xml
@@ -81,7 +81,12 @@
+
diff --git a/tests/AcDream.App.Tests/AcDream.App.Tests.csproj b/tests/AcDream.App.Tests/AcDream.App.Tests.csproj
index b8dc39980..9caeae5d2 100644
--- a/tests/AcDream.App.Tests/AcDream.App.Tests.csproj
+++ b/tests/AcDream.App.Tests/AcDream.App.Tests.csproj
@@ -24,6 +24,15 @@
+
+
@@ -52,6 +61,11 @@
PreserveNewest
+
+
diff --git a/tests/AcDream.App.Tests/UI/MossTankMarkupBuildOverRealFilesTests.cs b/tests/AcDream.App.Tests/UI/MossTankMarkupBuildOverRealFilesTests.cs
new file mode 100644
index 000000000..ca2faac54
--- /dev/null
+++ b/tests/AcDream.App.Tests/UI/MossTankMarkupBuildOverRealFilesTests.cs
@@ -0,0 +1,99 @@
+using AcDream.App.UI;
+using AcDream.Plugin.Abstractions;
+using AcDream.Plugins.MossTank;
+using Xunit;
+
+namespace AcDream.App.Tests.UI;
+
+///
+/// Fix round B item 15. Every existing MossTank markup pin
+/// (MossTankMarkupContractTests) validates mosstank*.xml
+/// against through reflection alone — "does a
+/// public property with this name and this CLR type exist" — never through
+/// itself, the code that actually mounts
+/// a plugin panel at runtime. MarkupDocument.Build has its own
+/// validation a reflection-only check can't see (attribute-format
+/// exceptions like ValidateArtStyle's "must be plain or retail",
+/// delegate-shape mismatches surfaced as thrown s
+/// rather than a missing property, 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. This builds every real mosstank*.xml file
+/// against a REAL (a stub ,
+/// same shape as MossTankMarkupContractTests.StubHost) so a bad
+/// attribute fails a test instead of dropping the panel silently at mount.
+///
+public sealed class MossTankMarkupBuildOverRealFilesTests
+{
+ private static string MossTankMarkupDirectory =>
+ Path.Combine(AppContext.BaseDirectory, "MossTank");
+
+ public static IEnumerable