diff --git a/src/AcDream.App/UI/UiMarkupList.cs b/src/AcDream.App/UI/UiMarkupList.cs
index 17d1d7045..ae28b1949 100644
--- a/src/AcDream.App/UI/UiMarkupList.cs
+++ b/src/AcDream.App/UI/UiMarkupList.cs
@@ -385,13 +385,22 @@ public sealed class UiMarkupList : UiElement
/// so a check column reads exactly like every other checkbox in the
/// client (contract requirement: reuse the toggle's own primitives rather
/// than a bespoke box-and-tick).
+ ///
+ ///
+ /// Fix round item 7: unlike text/icon cells (which draw nothing past
+ /// their own column's row count — there is no sensible default string or
+ /// icon), a check cell past its own bound data still draws the
+ /// UNCHECKED lamp. VVS materializes every cell in the row regardless of
+ /// which columns actually have data for it; docs/plugin-ui-markup.md
+ /// already documented this ("short columns simply have nothing to draw"
+ /// was never meant to apply to check specifically) — this makes the code
+ /// agree.
+ ///
///
private void DrawCheckCell(
UiRenderContext context, IReadOnlyList? flags, int index, float cellX, float y)
{
- if (flags is null || index >= flags.Count)
- return;
- bool isChecked = flags[index];
+ bool isChecked = flags is not null && index < flags.Count && flags[index];
Vector4 outer = isChecked ? UiMarkupToggle.CheckedOuter : UiMarkupToggle.UncheckedOuter;
Vector4 inner = isChecked ? UiMarkupToggle.CheckedInner : UiMarkupToggle.UncheckedInner;
UiMarkupToggle.DrawLamp(
diff --git a/tests/AcDream.App.Tests/UI/MarkupListColumnsTests.cs b/tests/AcDream.App.Tests/UI/MarkupListColumnsTests.cs
index fcacc8b9e..3c5e9b61d 100644
--- a/tests/AcDream.App.Tests/UI/MarkupListColumnsTests.cs
+++ b/tests/AcDream.App.Tests/UI/MarkupListColumnsTests.cs
@@ -672,6 +672,43 @@ public sealed class MarkupListColumnsTests
$"expected row 1's glyph ({uncheckedY}) below row 0's ({checkedY})");
}
+ // ── Fix round: short check columns draw the unchecked lamp past their
+ // own row count (fix item 7) ────────────────────────────────────────────
+
+ [Fact]
+ public void CheckColumn_DrawsUncheckedLamp_ForRowsPastItsOwnRowCount()
+ {
+ // col0 (text) has 3 rows, driving the overall row count. col1 (last,
+ // check) only binds 1 row of its own — VVS materializes every cell
+ // regardless (docs/plugin-ui-markup.md already documented this), so
+ // rows 1 and 2 must STILL draw an unchecked lamp, not nothing.
+ var list = new UiMarkupList
+ {
+ Width = 60f, Height = 60f, RowHeight = 18f,
+ SelectedIndexSource = () => -1,
+ BackgroundColor = default, BorderColor = default,
+ Columns = new[]
+ {
+ UiMarkupListColumn.Text(20f, () => new[] { "a", "b", "c" }, null),
+ UiMarkupListColumn.Check(20f, () => new[] { true }, _ => { }),
+ },
+ };
+ var (renderer, ctx) = MakeContext(200f, 200f);
+
+ list.DrawSelfAndChildren(ctx);
+
+ var uncheckedQuads = renderer.DebugSpriteSegmentVerts
+ .SelectMany(s => Chunk(s.Verts))
+ .Where(v => ColorMatches(v, UiMarkupToggle.UncheckedInner))
+ .ToList();
+ // One unchecked lamp for row 1 (bool value false — not this test's
+ // case) never applies here since col1's only row (0) is TRUE; rows 1
+ // and 2 have no bound value at all and must still draw UNCHECKED.
+ Assert.True(uncheckedQuads.Count >= 2,
+ $"expected an unchecked lamp for both row 1 and row 2 (past col1's own "
+ + $"1-row data), got {uncheckedQuads.Count} unchecked lamp quad(s)");
+ }
+
[Fact]
public void IconColumn_DrawsResolvedRowIcon_AndSkipsAMissingOne()
{