fix(ui): port exact paperdoll body selection

Resolve and sample retail's authored nine-color paperdoll click map, preserve local click coordinates, and select the stable highest-priority worn item with the player fallback. Keep targeted-use body clicks routed to self and pin both synthetic and live-DAT conformance.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-11 11:56:26 +02:00
parent 2644d1d527
commit 0b74d19475
16 changed files with 490 additions and 32 deletions

View file

@ -186,7 +186,9 @@ public sealed class RetailUiInteractionFlowTests
return interaction;
}
public void BindPaperdoll(ItemInteractionController? itemInteraction = null)
public void BindPaperdoll(
ItemInteractionController? itemInteraction = null,
PaperdollClickMap? clickMap = null)
=> PaperdollController.Bind(
Layout,
Objects,
@ -195,7 +197,23 @@ public sealed class RetailUiInteractionFlowTests
selection: Selection,
sendWield: (item, mask) => Wields.Add((item, mask)),
emptySlotSprite: 0x06004D20u,
itemInteraction: itemInteraction);
itemInteraction: itemInteraction,
clickMap: clickMap ?? SolidClickMap(0x00, 0x00, 0xFF));
public static PaperdollClickMap SolidClickMap(byte r, byte g, byte b)
{
const int width = 70;
const int height = 90;
var pixels = new byte[width * height * 4];
for (int i = 0; i < pixels.Length; i += 4)
{
pixels[i] = r;
pixels[i + 1] = g;
pixels[i + 2] = b;
pixels[i + 3] = 0xFF;
}
return new PaperdollClickMap(pixels, width, height);
}
public RetailUiAutomationProbe Probe()
=> new(Root, Objects);
@ -300,6 +318,33 @@ public sealed class RetailUiInteractionFlowTests
Assert.False(interaction.IsTargetModeActive);
}
[Fact]
public void PaperdollBodyClick_uncoveredPart_selectsPlayerThroughUiRoot()
{
var h = new Harness();
h.BindPaperdoll();
var probe = h.Probe();
Assert.True(probe.ClickElement(PaperdollController.DollDragMaskId));
Assert.Equal(Player, h.Selection.SelectedObjectId);
}
[Fact]
public void PaperdollBodyClick_coveredPart_selectsUpperEquippedItemThroughUiRoot()
{
var h = new Harness();
h.SeedHauberk();
h.Objects.MoveItem(Hauberk, Player, -1, HauberkMask);
h.BindPaperdoll(
clickMap: Harness.SolidClickMap(0x00, 0xFF, 0x00));
var probe = h.Probe();
Assert.True(probe.ClickElement(PaperdollController.DollDragMaskId));
Assert.Equal(Hauberk, h.Selection.SelectedObjectId);
}
[Fact]
public void InventoryTargetUse_invalidInventoryItem_isConsumedWithoutSelectionFallback()
{