So the cast maps EquipmentSlot.Weapon (0) → ItemEquipSlot.None (0), and every item equips only through the command slot one BELOW its real one:
EquipItemCommand { EquipmentSlot = Weapon } with a weapon (e.g. wooden_staff) → rejected
EquipItemCommand { EquipmentSlot = Armor } with that same weapon → accepted (equipment dict then stores the weapon under Armor)
Impact: clients sending the semantically correct slot can never equip anything; whatever works today works by accident of the off-by-one. Fix by mapping explicitly (switch) or aligning the enums — note EquipmentSlot.Armor is documented "Legacy - maps to Torso", so an explicit mapping function is probably the safe fix.
Until fixed, BaseZoneItemCommandTests pins the current off-by-one behavior as characterization tests referencing this issue — they will fail when the mapping is corrected (then flip the expectations).
Found by the coverage wave (spikersoft-backend PR #454) while testing `BaseZone.ProcessEquipItemCommand`.
`PlayerEntity.EquipItem` (SpikerSoft.Common `IGameEntity.cs` ~line 800) validates with:
```csharp
if (definition.EquipSlot != (ItemEquipSlot)(int)equipSlot && definition.EquipSlot != ItemEquipSlot.None)
return (false, null);
```
But the enums are misaligned by one:
- `EquipmentSlot` (IGameCommand.cs): `Weapon = 0, Armor = 1, HpPotion = 2, MpPotion = 3, ...`
- `ItemEquipSlot` (ItemDefinition.cs): `None = 0, Weapon = 1, Armor = 2, HpPotion = 3, MpPotion = 4`
So the cast maps `EquipmentSlot.Weapon (0)` → `ItemEquipSlot.None (0)`, and every item equips only through the command slot one BELOW its real one:
- `EquipItemCommand { EquipmentSlot = Weapon }` with a weapon (e.g. `wooden_staff`) → **rejected**
- `EquipItemCommand { EquipmentSlot = Armor }` with that same weapon → **accepted** (equipment dict then stores the weapon under `Armor`)
Impact: clients sending the semantically correct slot can never equip anything; whatever works today works by accident of the off-by-one. Fix by mapping explicitly (switch) or aligning the enums — note `EquipmentSlot.Armor` is documented "Legacy - maps to Torso", so an explicit mapping function is probably the safe fix.
Until fixed, `BaseZoneItemCommandTests` pins the current off-by-one behavior as characterization tests referencing this issue — they will fail when the mapping is corrected (then flip the expectations).
Verified 2026-08-07 against the synced default branch.
Code:spikersoft-backend@98102023 — still broken. SpikerSoft.Common/Models/GameServer/IGameEntity.cs:810 still does definition.EquipSlot != (ItemEquipSlot)(int)equipSlot, and SpikerSoft.Common/Models/GameServer/ItemDefinition.cs:19-26 still declares ItemEquipSlot { None, Weapon, Armor, HpPotion, MpPotion } — i.e. still offset by one from EquipmentSlot. The ordinal cast therefore still maps EquipmentSlot.Weapon (0) → ItemEquipSlot.None (0).
Live: Not directly probeable without an in-game equip. docker service ps spikersoft-gameserver_spikersoft-gameserver → 1/1 Running on SERVER, image redeployed 2026-08-07 12:25 UTC.
Remaining: Replace the ordinal cast with an explicit EquipmentSlot → ItemEquipSlot mapping (switch), noting EquipmentSlot.Armor is documented "Legacy - maps to Torso"; then flip the BaseZoneItemCommandTests characterization expectations that currently pin the off-by-one.
Status: still broken — full detail carried over to the new issue.
Closing here. Work now lives in the repo that holds the fix, so fixes #539 in a PR will
auto-close it on merge. The umbrella tracker keeps cross-repo epics only.
— Opus 5 Agent
Migrated to **spikerj/spikersoft-backend#539** as part of the umbrella-tracker breakup.
Verified 2026-08-07 against the synced default branch.
- **Code:** `spikersoft-backend@98102023` — still broken. `SpikerSoft.Common/Models/GameServer/IGameEntity.cs:810` still does `definition.EquipSlot != (ItemEquipSlot)(int)equipSlot`, and `SpikerSoft.Common/Models/GameServer/ItemDefinition.cs:19-26` still declares `ItemEquipSlot { None, Weapon, Armor, HpPotion, MpPotion }` — i.e. still offset by one from `EquipmentSlot`. The ordinal cast therefore still maps `EquipmentSlot.Weapon (0)` → `ItemEquipSlot.None (0)`.
- **Live:** Not directly probeable without an in-game equip. `docker service ps spikersoft-gameserver_spikersoft-gameserver` → 1/1 Running on SERVER, image redeployed 2026-08-07 12:25 UTC.
- **Remaining:** Replace the ordinal cast with an explicit `EquipmentSlot → ItemEquipSlot` mapping (switch), noting `EquipmentSlot.Armor` is documented "Legacy - maps to Torso"; then flip the `BaseZoneItemCommandTests` characterization expectations that currently pin the off-by-one.
Status: still broken — full detail carried over to the new issue.
Closing here. Work now lives in the repo that holds the fix, so `fixes #539` in a PR will
auto-close it on merge. The umbrella tracker keeps cross-repo epics only.
— Opus 5 Agent
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the coverage wave (spikersoft-backend PR #454) while testing
BaseZone.ProcessEquipItemCommand.PlayerEntity.EquipItem(SpikerSoft.CommonIGameEntity.cs~line 800) validates with:But the enums are misaligned by one:
EquipmentSlot(IGameCommand.cs):Weapon = 0, Armor = 1, HpPotion = 2, MpPotion = 3, ...ItemEquipSlot(ItemDefinition.cs):None = 0, Weapon = 1, Armor = 2, HpPotion = 3, MpPotion = 4So the cast maps
EquipmentSlot.Weapon (0)→ItemEquipSlot.None (0), and every item equips only through the command slot one BELOW its real one:EquipItemCommand { EquipmentSlot = Weapon }with a weapon (e.g.wooden_staff) → rejectedEquipItemCommand { EquipmentSlot = Armor }with that same weapon → accepted (equipment dict then stores the weapon underArmor)Impact: clients sending the semantically correct slot can never equip anything; whatever works today works by accident of the off-by-one. Fix by mapping explicitly (switch) or aligning the enums — note
EquipmentSlot.Armoris documented "Legacy - maps to Torso", so an explicit mapping function is probably the safe fix.Until fixed,
BaseZoneItemCommandTestspins the current off-by-one behavior as characterization tests referencing this issue — they will fail when the mapping is corrected (then flip the expectations).Migrated to spikerj/spikersoft-backend#539 as part of the umbrella-tracker breakup.
Verified 2026-08-07 against the synced default branch.
spikersoft-backend@98102023— still broken.SpikerSoft.Common/Models/GameServer/IGameEntity.cs:810still doesdefinition.EquipSlot != (ItemEquipSlot)(int)equipSlot, andSpikerSoft.Common/Models/GameServer/ItemDefinition.cs:19-26still declaresItemEquipSlot { None, Weapon, Armor, HpPotion, MpPotion }— i.e. still offset by one fromEquipmentSlot. The ordinal cast therefore still mapsEquipmentSlot.Weapon (0)→ItemEquipSlot.None (0).docker service ps spikersoft-gameserver_spikersoft-gameserver→ 1/1 Running on SERVER, image redeployed 2026-08-07 12:25 UTC.EquipmentSlot → ItemEquipSlotmapping (switch), notingEquipmentSlot.Armoris documented "Legacy - maps to Torso"; then flip theBaseZoneItemCommandTestscharacterization expectations that currently pin the off-by-one.Status: still broken — full detail carried over to the new issue.
Closing here. Work now lives in the repo that holds the fix, so
fixes #539in a PR willauto-close it on merge. The umbrella tracker keeps cross-repo epics only.
— Opus 5 Agent