fix(D.2b): correct EquipMask to canonical retail INVENTORY_LOC + numeric-pin test

The old enum invented two phantom bits (HandArmor=0x2000, FootArmor=0x10000) and
used non-retail names (Necklace, LeftBracelet, RightBracelet, LeftRing, RightRing,
AetheriaRed/Yellow/Blue), shifting every slot above 0x1000 out of alignment with
retail INVENTORY_LOC (acclient.h:3193) and ACE EquipMask.

Replace the enum body with verbatim retail values. Add EquipMaskTests to
numeric-pin every member so future renumbering breaks at compile/test time.

Existing consumers (ClientObjectTable, InventoryController, GameEventWiringTests,
ClientObjectTableTests) only reference EquipMask.MeleeWeapon and EquipMask.None --
both present at their correct retail values -- so no call sites needed updating.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-22 21:56:06 +02:00
parent bd4b49f810
commit 1810c71f5c
2 changed files with 90 additions and 36 deletions

View file

@ -57,46 +57,46 @@ public enum ItemType : uint
}
/// <summary>
/// Equipment slot bitmask. 31 slots from head to Aetheria. Paperdoll
/// widget offsets <c>+0x604..+0x660</c> in the retail panel correspond
/// to these bits 1:1 (see r06 §2 and UI slice 05 paperdoll section).
/// Equipment slot bitmask — the verbatim retail <c>INVENTORY_LOC</c> enum
/// (docs/research/named-retail/acclient.h:3193; identical to ACE's EquipMask).
/// The wire (ValidLocations / CurrentWieldedLocation / WieldObject EquipLoc) delivers
/// these exact bits. Pinned by EquipMaskTests — do NOT renumber.
/// </summary>
[Flags]
public enum EquipMask : uint
{
None = 0,
HeadWear = 0x00000001,
ChestWear = 0x00000002,
AbdomenWear = 0x00000004,
UpperArmWear = 0x00000008,
LowerArmWear = 0x00000010,
HandWear = 0x00000020,
UpperLegWear = 0x00000040,
LowerLegWear = 0x00000080,
FootWear = 0x00000100,
ChestArmor = 0x00000200,
AbdomenArmor = 0x00000400,
UpperArmArmor = 0x00000800,
LowerArmArmor = 0x00001000,
HandArmor = 0x00002000,
UpperLegArmor = 0x00004000,
LowerLegArmor = 0x00008000,
FootArmor = 0x00010000,
Necklace = 0x00020000,
LeftBracelet = 0x00040000,
RightBracelet = 0x00080000,
LeftRing = 0x00100000,
RightRing = 0x00200000,
MeleeWeapon = 0x00400000,
Shield = 0x00800000,
MissileWeapon = 0x01000000,
Held = 0x02000000, // lit torch, book in hand
MissileAmmo = 0x04000000,
Cloak = 0x08000000,
TrinketOne = 0x10000000,
AetheriaRed = 0x20000000,
AetheriaYellow= 0x40000000,
AetheriaBlue = 0x80000000u,
None = 0x00000000,
HeadWear = 0x00000001,
ChestWear = 0x00000002,
AbdomenWear = 0x00000004,
UpperArmWear = 0x00000008,
LowerArmWear = 0x00000010,
HandWear = 0x00000020,
UpperLegWear = 0x00000040,
LowerLegWear = 0x00000080,
FootWear = 0x00000100,
ChestArmor = 0x00000200,
AbdomenArmor = 0x00000400,
UpperArmArmor = 0x00000800,
LowerArmArmor = 0x00001000,
UpperLegArmor = 0x00002000,
LowerLegArmor = 0x00004000,
NeckWear = 0x00008000,
WristWearLeft = 0x00010000,
WristWearRight = 0x00020000,
FingerWearLeft = 0x00040000,
FingerWearRight= 0x00080000,
MeleeWeapon = 0x00100000,
Shield = 0x00200000,
MissileWeapon = 0x00400000,
MissileAmmo = 0x00800000,
Held = 0x01000000,
TwoHanded = 0x02000000,
TrinketOne = 0x04000000,
Cloak = 0x08000000,
SigilOne = 0x10000000,
SigilTwo = 0x20000000,
SigilThree = 0x40000000,
}
/// <summary>

View file

@ -0,0 +1,54 @@
using AcDream.Core.Items;
using Xunit;
namespace AcDream.Core.Tests.Items;
/// <summary>
/// Pins every EquipMask member to the verbatim retail INVENTORY_LOC value
/// (docs/research/named-retail/acclient.h:3193). The wire delivers these exact bits
/// (ACE EquipMask == retail INVENTORY_LOC); any drift desyncs the paperdoll element-id→mask
/// map and the GetAndWieldItem wire. This test is the anti-regression lock.
/// </summary>
public sealed class EquipMaskTests
{
[Theory]
[InlineData(0x00000000u, EquipMask.None)]
[InlineData(0x00000001u, EquipMask.HeadWear)]
[InlineData(0x00000002u, EquipMask.ChestWear)]
[InlineData(0x00000004u, EquipMask.AbdomenWear)]
[InlineData(0x00000008u, EquipMask.UpperArmWear)]
[InlineData(0x00000010u, EquipMask.LowerArmWear)]
[InlineData(0x00000020u, EquipMask.HandWear)]
[InlineData(0x00000040u, EquipMask.UpperLegWear)]
[InlineData(0x00000080u, EquipMask.LowerLegWear)]
[InlineData(0x00000100u, EquipMask.FootWear)]
[InlineData(0x00000200u, EquipMask.ChestArmor)]
[InlineData(0x00000400u, EquipMask.AbdomenArmor)]
[InlineData(0x00000800u, EquipMask.UpperArmArmor)]
[InlineData(0x00001000u, EquipMask.LowerArmArmor)]
[InlineData(0x00002000u, EquipMask.UpperLegArmor)]
[InlineData(0x00004000u, EquipMask.LowerLegArmor)]
[InlineData(0x00008000u, EquipMask.NeckWear)]
[InlineData(0x00010000u, EquipMask.WristWearLeft)]
[InlineData(0x00020000u, EquipMask.WristWearRight)]
[InlineData(0x00040000u, EquipMask.FingerWearLeft)]
[InlineData(0x00080000u, EquipMask.FingerWearRight)]
[InlineData(0x00100000u, EquipMask.MeleeWeapon)]
[InlineData(0x00200000u, EquipMask.Shield)]
[InlineData(0x00400000u, EquipMask.MissileWeapon)]
[InlineData(0x00800000u, EquipMask.MissileAmmo)]
[InlineData(0x01000000u, EquipMask.Held)]
[InlineData(0x02000000u, EquipMask.TwoHanded)]
[InlineData(0x04000000u, EquipMask.TrinketOne)]
[InlineData(0x08000000u, EquipMask.Cloak)]
[InlineData(0x10000000u, EquipMask.SigilOne)]
[InlineData(0x20000000u, EquipMask.SigilTwo)]
[InlineData(0x40000000u, EquipMask.SigilThree)]
public void Member_has_canonical_retail_value(uint expected, EquipMask member)
=> Assert.Equal(expected, (uint)member);
[Fact]
public void Weapon_ready_slot_composite_is_0x3500000() // acclient.h:3235 WEAPON_READY_SLOT_LOC
=> Assert.Equal(0x3500000u,
(uint)(EquipMask.MeleeWeapon | EquipMask.MissileWeapon | EquipMask.Held | EquipMask.TwoHanded));
}