acdream/tests/AcDream.App.Tests/UI/UiItemSlotTests.cs
Erik a7cad5566b fix(D.5.1): occupancy-gated slot numbers (empty=0x1000005e bg digit) + bottom-right rect probe
FIX 1: UIElement_UIItem::SetShortcutNum (decomp 229481) has a three-way source
branch: occupied+peace -> 0x10000042 (peace digit set), occupied+war -> 0x10000043
(war digit set), empty (ItemId==0) -> 0x1000005e (background digit, stance-independent).
acdream previously only had the peace/war pair and drew them regardless of occupancy.

Changes:
- GameWindow.cs: read property 0x1000005e into toolbarEmptyDigits (no fallback;
  null is safe). Logs entry count. Passes emptyDigits to Bind. Adds [D.5.1 probe]
  block logging screen pos + size of 7 bottom-right element ids via ScreenPosition.
- ToolbarController.cs: adds _emptyDigits field, emptyDigits ctor+Bind param (null
  default). RestampShortcutNumbers sets cell.EmptyDigits. Comments cite decomp 229481.
- UiItemSlot.cs: adds EmptyDigits property + ActiveDigitArray() internal testable seam
  (occupied -> peace/war by stance; empty -> EmptyDigits). OnDraw uses it. Comment
  updated with three-way source table.
- Tests: 5 new UiItemSlotTests (ActiveDigitArray occupancy), 2 new
  ToolbarControllerTests (emptyDigits injection + null-safe).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:27:27 +02:00

144 lines
4.5 KiB
C#

using AcDream.App.UI;
namespace AcDream.App.Tests.UI;
public class UiItemSlotTests
{
[Fact]
public void IsLeafWidget()
=> Assert.True(new UiItemSlot().ConsumesDatChildren);
[Fact]
public void DefaultEmptySprite_isToolbarBorder()
=> Assert.Equal(0x060074CFu, new UiItemSlot().EmptySprite);
[Fact]
public void Empty_whenNoItem()
{
var s = new UiItemSlot();
Assert.Equal(0u, s.ItemId);
Assert.Equal(0u, s.IconTexture);
}
[Fact]
public void SetItem_setsIdAndTexture()
{
var s = new UiItemSlot();
s.SetItem(0x5001u, 0x99u);
Assert.Equal(0x5001u, s.ItemId);
Assert.Equal(0x99u, s.IconTexture);
}
[Fact]
public void Clear_afterSetItem_resetsToEmpty()
{
var s = new UiItemSlot();
s.SetItem(0x5001u, 0x99u);
s.Clear();
Assert.Equal(0u, s.ItemId);
Assert.Equal(0u, s.IconTexture);
}
// ── Shortcut number tests ────────────────────────────────────────────────
// Port of UIElement_UIItem::SetShortcutNum (acclient_2013_pseudo_c.txt:229465).
[Fact]
public void ShortcutNum_defaultIsMinusOne()
{
var s = new UiItemSlot();
Assert.Equal(-1, s.ShortcutNum);
}
[Fact]
public void ShortcutPeace_defaultIsTrue()
{
var s = new UiItemSlot();
Assert.True(s.ShortcutPeace);
}
[Fact]
public void SetShortcutNum_setsIndexAndPeace()
{
var s = new UiItemSlot();
s.SetShortcutNum(3, peace: false);
Assert.Equal(3, s.ShortcutNum);
Assert.False(s.ShortcutPeace);
}
[Fact]
public void SetShortcutNum_peaceTrue()
{
var s = new UiItemSlot();
s.SetShortcutNum(0, peace: true);
Assert.Equal(0, s.ShortcutNum);
Assert.True(s.ShortcutPeace);
}
[Fact]
public void ClearShortcutNum_setsMinusOne()
{
var s = new UiItemSlot();
s.SetShortcutNum(5, peace: true);
s.ClearShortcutNum();
Assert.Equal(-1, s.ShortcutNum);
}
// ── ActiveDigitArray occupancy gating (decomp UIElement_UIItem::SetShortcutNum:229481) ──
private static readonly uint[] Peace = { 0x10u, 0x11u, 0x12u };
private static readonly uint[] War = { 0x20u, 0x21u, 0x22u };
private static readonly uint[] Empty = { 0x30u, 0x31u, 0x32u };
/// <summary>
/// When ItemId == 0 (empty slot), ActiveDigitArray returns EmptyDigits regardless
/// of ShortcutPeace. Retail ref: UIElement_UIItem::SetShortcutNum (decomp 229481) —
/// else branch when m_elem_Icon->m_state == 0x1000001c (empty).
/// </summary>
[Fact]
public void ActiveDigitArray_emptySlot_returnsEmptyDigits()
{
var s = new UiItemSlot { PeaceDigits = Peace, WarDigits = War, EmptyDigits = Empty };
s.SetShortcutNum(0, peace: true);
// ItemId == 0 → EmptyDigits
Assert.Same(Empty, s.ActiveDigitArray());
}
[Fact]
public void ActiveDigitArray_emptySlot_warStance_stillReturnsEmptyDigits()
{
var s = new UiItemSlot { PeaceDigits = Peace, WarDigits = War, EmptyDigits = Empty };
s.SetShortcutNum(0, peace: false);
// ItemId == 0 → EmptyDigits regardless of stance
Assert.Same(Empty, s.ActiveDigitArray());
}
/// <summary>
/// When ItemId != 0 (occupied), ActiveDigitArray returns PeaceDigits or WarDigits
/// depending on ShortcutPeace. Retail ref: UIElement_UIItem::SetShortcutNum (decomp 229481/229493).
/// </summary>
[Fact]
public void ActiveDigitArray_occupiedSlot_peaceStance_returnsPeaceDigits()
{
var s = new UiItemSlot { PeaceDigits = Peace, WarDigits = War, EmptyDigits = Empty };
s.SetItem(0x5001u, 0x99u);
s.SetShortcutNum(0, peace: true);
Assert.Same(Peace, s.ActiveDigitArray());
}
[Fact]
public void ActiveDigitArray_occupiedSlot_warStance_returnsWarDigits()
{
var s = new UiItemSlot { PeaceDigits = Peace, WarDigits = War, EmptyDigits = Empty };
s.SetItem(0x5001u, 0x99u);
s.SetShortcutNum(0, peace: false);
Assert.Same(War, s.ActiveDigitArray());
}
[Fact]
public void ActiveDigitArray_emptySlot_nullEmptyDigits_returnsNull()
{
var s = new UiItemSlot { PeaceDigits = Peace, WarDigits = War, EmptyDigits = null };
s.SetShortcutNum(0, peace: true);
Assert.Null(s.ActiveDigitArray());
}
}