fix(D.2b): Character window — tab bar sprites on root + footer State-A all 3 lines

BUG 1 (tab bar): Tab group elements (0x10000228/229/538) are UiText with
ConsumesDatChildren=true so their 3 button children are consumed at import.
Fix: inject 3 sprite UiTexts per tab as CHILDREN OF LAYOUT ROOT at absolute
tab rects, ZOrder=8/9 so they draw over dat-imported UiTexts (ZOrder=1-3).
Original tab groups hidden. Active tab (Attributes) gold; inactive parchment.

BUG 2 (footer): Three root causes, all fixed.
  (a) _byId stores LAST registered copy per id: stateA (0x10000240) was
      the Titles-page copy, hidden by the page-visibility pass. Fixed by
      walking root.Children to find the Attributes page (contains NameId)
      then FindInSubtree for stateA within that subtree.
  (b) Attributes-page stateB/stateC siblings (stacked at y=545) were still
      Visible=True, drawing over stateA line-1/line-2. Fixed with
      HideAllById walking the Attributes page subtree for ids 241/247.
  (c) Footer label elements (H=17-18px, Padding=4f) were routed through
      UiTexts scroll path: bottom-pinned baseY ended above the top clip
      boundary, silently blanking all text. Fixed: LabelProvider sets
      Padding=0f for directly-bound footer single-line labels.

UiDatElement.ElementId exposes _info.Id for subtree id-based walks.
676 tests pass, vitals panel unaffected (regression screenshot clean).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-25 22:59:44 +02:00
parent 99291595bb
commit 5aa65dbd43
3 changed files with 236 additions and 111 deletions

View file

@ -514,71 +514,72 @@ public class CharacterStatControllerTests
}
// ── Pass 2: Tab button states ─────────────────────────────────────────────
// Tab groups are UiText (Type 12) with ConsumesDatChildren=true; their button
// children are consumed at import time. AddTabSprites() injects 3 UiText
// sprite-children per group when a spriteResolve is provided.
// Tab sprites are added to layout.Root (NOT to the tab group elements), so
// the tab groups keep Children.Count==0 and survive the page-visibility pass.
// AddTabSpritesToRoot() adds 3 sprite UiTexts + 1 label UiText per tab to the
// root when a spriteResolve is provided; nothing is added when spriteResolve=null.
[Fact]
public void TabButtons_NoSpriteResolve_AddsNoSpriteChildren()
public void TabButtons_NoSpriteResolve_AddsNoSpriteChildrenToRoot()
{
// When spriteResolve is null, AddTabSprites returns early — no children added.
var tabGroup = new UiText();
var layout = Fake((CharacterStatController.TabAttribId, tabGroup));
// When spriteResolve is null, AddTabSpritesToRoot is not called — no sprites added.
var layout = Fake(); // empty fake layout with just the root UiPanel
int rootChildCountBefore = layout.Root.Children.Count;
CharacterStatController.Bind(layout, SampleData.SampleCharacter, spriteResolve: null);
Assert.Empty(tabGroup.Children);
// No extra children added to root when spriteResolve is null.
Assert.Equal(rootChildCountBefore, layout.Root.Children.Count);
}
[Fact]
public void TabButtons_AttributesGroup_AddsThreeOpenSpriteChildren()
public void TabButtons_AttributesGroup_AddsOpenSpriteIdsToRoot()
{
// Attributes tab (isOpen=true): three UiText sprite children with Open sprite ids.
var tabGroup = new UiText();
var layout = Fake((CharacterStatController.TabAttribId, tabGroup));
// Attributes tab (isOpen=true): 3 sprite children with Open sprite ids on ROOT.
// The tab group element itself has 0 children (sprites go to root, not the group).
var layout = Fake(); // minimal fake
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
var sprites = tabGroup.Children.OfType<UiText>().ToList();
Assert.Equal(3, sprites.Count);
Assert.Equal(0x06005D92u, sprites[0].BackgroundSprite); // Open left-cap
Assert.Equal(0x06005D94u, sprites[1].BackgroundSprite); // Open center
Assert.Equal(0x06005D96u, sprites[2].BackgroundSprite); // Open right-cap
// Root should contain the Open sprite ids among all tab sprite children.
var sprites = layout.Root.Children.OfType<UiText>()
.Where(t => t.BackgroundSprite != 0u).ToList();
Assert.Contains(sprites, t => t.BackgroundSprite == 0x06005D92u); // Open left-cap
Assert.Contains(sprites, t => t.BackgroundSprite == 0x06005D94u); // Open center
Assert.Contains(sprites, t => t.BackgroundSprite == 0x06005D96u); // Open right-cap
}
[Fact]
public void TabButtons_SkillsGroup_AddsThreeClosedSpriteChildren()
public void TabButtons_SkillsAndTitles_AddClosedSpriteIdsToRoot()
{
// Skills tab (isOpen=false): three UiText sprite children with Closed sprite ids.
var tabGroup = new UiText();
var layout = Fake((CharacterStatController.TabSkillsId, tabGroup));
// Skills + Titles tabs (isOpen=false): Closed sprite ids appear on root.
var layout = Fake();
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
var sprites = tabGroup.Children.OfType<UiText>().ToList();
Assert.Equal(3, sprites.Count);
Assert.Equal(0x06005D93u, sprites[0].BackgroundSprite); // Closed left-cap
Assert.Equal(0x06005D95u, sprites[1].BackgroundSprite); // Closed center
Assert.Equal(0x06005D97u, sprites[2].BackgroundSprite); // Closed right-cap
var sprites = layout.Root.Children.OfType<UiText>()
.Where(t => t.BackgroundSprite != 0u).ToList();
Assert.Contains(sprites, t => t.BackgroundSprite == 0x06005D93u); // Closed left-cap
Assert.Contains(sprites, t => t.BackgroundSprite == 0x06005D95u); // Closed center
Assert.Contains(sprites, t => t.BackgroundSprite == 0x06005D97u); // Closed right-cap
}
[Fact]
public void TabButtons_TitlesGroup_AddsThreeClosedSpriteChildren()
public void TabButtons_WithSpriteResolve_AddsAllThreeTabsToRoot()
{
// Titles tab (isOpen=false): three UiText sprite children with Closed sprite ids.
var tabGroup = new UiText();
var layout = Fake((CharacterStatController.TabTitlesId, tabGroup));
// With spriteResolve: all 3 tabs inject 4 children each (3 sprites + 1 label)
// = 12 sprite children total across 3 tabs on the root.
var layout = Fake();
CharacterStatController.Bind(layout, SampleData.SampleCharacter,
spriteResolve: id => (id, 16, 16));
var sprites = tabGroup.Children.OfType<UiText>().ToList();
Assert.Equal(3, sprites.Count);
Assert.Equal(0x06005D93u, sprites[0].BackgroundSprite); // Closed left-cap
Assert.Equal(0x06005D95u, sprites[1].BackgroundSprite); // Closed center
Assert.Equal(0x06005D97u, sprites[2].BackgroundSprite); // Closed right-cap
// 3 tabs × 3 sprites = 9 sprite UiTexts on root.
var sprites = layout.Root.Children.OfType<UiText>()
.Where(t => t.BackgroundSprite != 0u).ToList();
Assert.Equal(9, sprites.Count);
}
// ── Affordability helpers (GetRaiseCost) ──────────────────────────────────