adventure: fix concurrency + dup hazards in web equip path

Code review of the Ask-7 web equipment-management path surfaced three
correctness issues, all fixed here:

- applyEquipOrder ran the poll-goroutine equip mutations without the
  per-user advUserLock that every Matrix-side mutation (!give, !equip,
  arena, …) holds, so the lock gave no mutual exclusion against the web
  path. A concurrent !give of the item being equipped could duplicate it.
  Now takes advUserLock(owner) for the whole apply, matching the DM path.

- applyMasterworkEquip evicted the displaced occupant to the pack BEFORE
  the destructive slot write, so a fault left the piece both worn and in
  the pack — and the 30s equip poll retry re-evicted it every tick. Now
  removes the incoming row, writes the slot, then re-packs the occupant
  last as a best-effort step: once the slot no longer references it, the
  re-pack cannot duplicate, and a failure is logged not aborted on (the
  DM confirm handler's tolerance).

- PlayerDetail.Balance dropped omitempty: a real €0 balance is an
  informative fact, not an absent one, and omitting it left the web
  confirm dialog with no balance to show.
This commit is contained in:
prosolis
2026-07-17 21:04:38 -07:00
parent 68c8cdff2d
commit 5a8d21f780
3 changed files with 40 additions and 13 deletions

View File

@@ -393,7 +393,9 @@ type PlayerDetail struct {
// CanTakeOff), not in Equipped, which stays magic-only (the DnD slots).
Slots []EquipSlotView `json:"slots,omitempty"`
// Balance is the owner's euro balance, for the web's upgrade/repair confirm.
Balance float64 `json:"balance,omitempty"`
// No omitempty: a €0 balance is a real, informative fact (a broke player), not
// an absent one — dropping it would let the confirm dialog show a stale amount.
Balance float64 `json:"balance"`
}
// EquipSlotView is one of the 5 standard equipment slots, carrying what the web