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

@ -0,0 +1,113 @@
using AcDream.Core.Items;
namespace AcDream.Core.Tests.Items;
public sealed class PaperdollSelectionPolicyTests
{
private const uint Player = 0x50000001u;
[Fact]
public void UncoveredValidBodyPart_returnsPlayer()
{
var objects = TableWithPlayer();
uint selected = PaperdollSelectionPolicy.GetUpperInventoryObject(
objects,
Player,
EquipMask.HeadWear);
Assert.Equal(Player, selected);
}
[Fact]
public void DefaultPriorityArmor_beatsClothingOnSameBodyPart()
{
var objects = TableWithPlayer();
AddEquipped(objects, 0x50001001u, EquipMask.ChestWear, priority: 20u);
AddEquipped(objects, 0x50001002u, EquipMask.ChestArmor, priority: 0u);
uint selected = PaperdollSelectionPolicy.GetUpperInventoryObject(
objects,
Player,
EquipMask.ChestWear | EquipMask.ChestArmor);
Assert.Equal(0x50001002u, selected);
}
[Fact]
public void HigherExplicitPriorityWins_andTiesPreserveListOrder()
{
var objects = TableWithPlayer();
AddEquipped(objects, 0x50001001u, EquipMask.HeadWear, priority: 12u);
AddEquipped(objects, 0x50001002u, EquipMask.HeadWear, priority: 12u);
AddEquipped(objects, 0x50001003u, EquipMask.HeadWear, priority: 13u);
uint selected = PaperdollSelectionPolicy.GetUpperInventoryObject(
objects,
Player,
EquipMask.HeadWear);
Assert.Equal(0x50001003u, selected);
objects.Remove(0x50001003u);
selected = PaperdollSelectionPolicy.GetUpperInventoryObject(
objects,
Player,
EquipMask.HeadWear);
Assert.Equal(0x50001001u, selected);
}
[Fact]
public void DefaultArmorPriority_usesCoveredIntersectionRangeExactly()
{
var objects = TableWithPlayer();
AddEquipped(objects, 0x50001001u, EquipMask.LowerLegWear, priority: 1u);
AddEquipped(
objects,
0x50001002u,
EquipMask.LowerLegWear | EquipMask.LowerLegArmor,
priority: 0u);
uint selected = PaperdollSelectionPolicy.GetUpperInventoryObject(
objects,
Player,
EquipMask.LowerLegWear | EquipMask.LowerLegArmor);
// Retail compares the covered value 0x4080 to the inclusive
// 0x0200..0x4000 normalization range, so this combined placement stays 0.
Assert.Equal(0x50001001u, selected);
}
[Fact]
public void InvalidBodyMask_returnsZero()
{
var objects = TableWithPlayer();
Assert.Equal(0u, PaperdollSelectionPolicy.GetUpperInventoryObject(
objects,
Player,
EquipMask.None));
}
private static ClientObjectTable TableWithPlayer()
{
var objects = new ClientObjectTable();
objects.AddOrUpdate(new ClientObject { ObjectId = Player, Type = ItemType.Creature });
return objects;
}
private static void AddEquipped(
ClientObjectTable objects,
uint objectId,
EquipMask location,
uint priority)
{
objects.AddOrUpdate(new ClientObject
{
ObjectId = objectId,
WielderId = Player,
CurrentlyEquippedLocation = location,
Priority = priority,
});
}
}