fix(CT-GF1): UiLabel opts out of the self-clip — plugin markup text restored

CT7 gate regression (owner report): all MossTank plugin text vanished
except button captions. A markup <label> authors position only, so
UiLabel's box was degenerate (0x0) and CT-GF1's completed self-clip
(UIRegion::DrawHere @0x0069FA30 shape) cropped its glyphs to nothing;
markup buttons author w/h, which is why their captions survived.

UiLabel now opts out of the self-clip — it is ClickThrough pure text
whose real containment is its ancestors (the plugin panel/window, which
are properly sized), the effective retail behavior for a text region
whose box hugs its glyphs — and keeps a truthful box by measuring its
current text each draw. Mechanism pin: an unsized label's subtree must
render inside its sized parent (probe-child draw-capture test).

Gate note recorded by the owner in the same round: the Titles-page
divider IS visible inside the window in retail while scrolling — a
retail quirk our clipped rendering now reproduces exactly. CT7 gate
PASSED apart from this regression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-25 07:28:26 +02:00
parent 9e85d82325
commit 752782d0a9
2 changed files with 68 additions and 0 deletions

View file

@ -376,4 +376,50 @@ public sealed class UiAncestorClipTests
root.DrawOverlays(ctx);
Assert.NotEmpty(renderer.DebugSpriteSegmentVerts);
}
/// <summary>
/// CT7 gate regression (2026-08-25): the MossTank plugin's markup
/// labels lost ALL text under the completed self-clip — a markup
/// &lt;label&gt; authors position only, so UiLabel's box was 0x0 and the
/// element's own clip cropped every glyph. UiLabel now opts out of the
/// self-clip (its ancestors clip — the retail containment for a text
/// region whose box hugs its glyphs) and keeps a truthful measured box.
/// Button captions never regressed because markup buttons author w/h.
/// </summary>
[Fact]
public void UnsizedMarkupLabel_InsideASizedPanel_DoesNotSelfClipItsSubtree()
{
var (device, renderer, ctx) = MakeContext(800f, 600f);
var panel = new UiPanel
{
Left = 50f, Top = 50f, Width = 200f, Height = 100f,
BackgroundColor = default, BorderColor = default,
};
// Exactly what MarkupDocument builds for <label x=".." y=".."> —
// position only, no Width/Height (the box is degenerate at draw
// time). Pre-fix, UiLabel inherited the ambient self-clip and its
// 0x0 push cropped everything it painted to nothing — the MossTank
// vanished-text regression. The mechanism pin below hangs a
// sprite-drawing probe child under the label: with the self-clip
// the probe cannot render; with UiLabel's opt-out it must.
var label = new UiLabel { Left = 10f, Top = 10f, Text = "MossTank status line" };
var probe = new UiPanel
{
Left = 0f, Top = 0f, Width = 100f, Height = 10f,
BackgroundSprite = 42u,
SpriteResolve = id => id == 42u ? (42u, 8, 8) : (0u, 0, 0),
BackgroundColor = default, BorderColor = default,
};
label.AddChild(probe);
panel.AddChild(label);
panel.DrawSelfAndChildren(ctx);
Assert.True(
AnyQuadAt(renderer, (x, y) => x >= 60f && x <= 160f && y >= 60f && y <= 70f),
"the unsized label self-clipped its subtree away (MossTank regression)");
// The box became truthful (fallback-measured without a font), not degenerate.
Assert.True(label.Width > 0f && label.Height > 0f,
$"label box stayed degenerate: {label.Width}x{label.Height}");
}
}