Commit Graph

2 Commits

Author SHA1 Message Date
prosolis
5a8d21f780 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.
2026-07-17 21:04:38 -07:00
prosolis
68c8cdff2d adventure: ask 7 — apply web equipment management (poll half)
Mirror of Pete's ask 7. gogobee polls the equip queue and applies the new
actions against the five standard gear slots:
  - equip: routes MasterworkGear/ArenaGear to applyMasterworkEquip (evicts
    any special occupant back to the pack; downgrade-blocked), else the
    existing applyMagicEquip.
  - unequip: EquipmentSlot vocabulary -> applyMasterworkUnequip (resets the
    slot to its tier-0 default, keeps the row), else applyMagicUnequip.
  - upgrade: purchaseEquipmentTier, euro-idempotent (DebitIdem keyed on the
    order GUID), downgrade + max-tier guarded.
  - repair: repair(), euro-idempotent, recomputes blacksmithRepairCost.

Detail push now carries Slots (EquipSlotView x5) + Balance; itemViews gives
masterwork/arena backpack rows an equip id; the compare decorator is guarded
to magic-only. buildDetailSnapshot is a method so it can read the euro balance.

Retry-safety: no CreditIdem refund on a later save fault (would double-pay a
guid-guarded retry) — we return retry=true and let the next poll re-run, since
the debit is guid-idempotent and the slot write is idempotent. Matches the
casino escrow precedent. Unit tests cover downgrade block, max-tier,
insufficient funds, idempotent replay, eviction, and take-off reset.

Deploy AFTER Pete: Pete's ingest must accept the new verdict strings before
this side emits them.
2026-07-17 20:34:56 -07:00