feat: secure trade with other players - wire, RuntimeTradeState, the

authored gmSecureTradeUI window, and both retail open paths

Three-lane research first (docs/research/2026-08-14-trade-lane{A,B,C}):
retail gmSecureTradeUI decode, the byte-exact ACE/decomp/holtburger
three-way wire agreement, and the acdream seam map (which found both
open paths ALREADY classified by the ported policy - OpenSecureTrade on
Use-a-player, StartSecureTrade on drag-item-onto-player with the
DragItemOnPlayerOpensSecureTrade option - dead-ending at a stub toast).

- Core.Net: TradeRequests builders (0x1F6-0x204, retail's CM_Trade
  senders byte-checked against ACE's readers; the ACE-discarded
  AcceptTrade echo carries zero-count item lists - AD-94), corrected +
  completed inbound parsers (0x1FD-0x208; the old AddToTrade parser
  missed the SIDE dword, TradeFailure missed the reason), delegate-hole
  registrars, six WorldSession sends. 10 golden-byte tests.
- Runtime: RuntimeTradeState, the third sibling J-owner (fellowship/
  allegiance shape): session-scoped, clears at generation reset (new
  stage Trade=14), staged teardown stage 11 (Identity/EntityObjects
  shift 12/13, TeardownStageCount 14 - the FA2-era per-stage-flag test
  caught the mapping exactly as designed), combined ownership ledger,
  event routing with ACE's wrong-initiator RegisterTrade landmine
  honored (partner = whichever guid is not mine). 7 conformance tests.
- App: SecureTradeUiController binds the dedicated authored LayoutDesc
  0x2100000D (root 0x1000007A - gmSecureTradeUI::PostInit's exact ids):
  partner name/status/count/grid, the authored 'Trade' accept toggle
  (accept <-> decline withdraw), 'Clear All' (ACE clears BOTH sides -
  surfaced honestly), the X close, drop-on-your-grid staging, per-mode
  accept cues (partner icon's authored Highlight state + Trade button
  Selected latch). Mounted via the vendor recipe (nine-slice chrome,
  hidden until RegisterTrade). ItemInteractionController's two policy
  arms now raise SecureTradeRequested instead of the stub toast; the
  drag path queues the dragged item until the window registers
  (ClientTradeSystem::AttemptToTradeItem @0x0056DF80's shape).

Register: AD-94 (accept-echo zero-count lists), AD-95 (numeric-only
count texts pending template verification).

Suites: App 4,990/3, Core.Net 905, Runtime 1,626 - all green. The
panel itself is user-gate acceptance (two-client connected trade), the
#372-class lesson: fixture-green alone is not acceptance for a mount.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-14 11:49:13 +02:00
parent bee38b0746
commit 067cbea8a5
26 changed files with 2682 additions and 42 deletions

View file

@ -2561,6 +2561,65 @@ public sealed class WorldSession : IDisposable
SendGameAction(InventoryActions.BuildDropItem(seq, itemGuid));
}
// ── Secure trade (docs/research/2026-08-14-trade-laneB-wire.md) ────────
/// <summary>Open secure trade with another player — retail
/// <c>CM_Trade::Event_OpenTradeNegotiations @ 0x0056D300</c>.</summary>
public void SendOpenTradeNegotiations(uint partnerGuid)
{
uint seq = NextGameActionSequence();
SendGameAction(TradeRequests.BuildOpenTradeNegotiations(seq, partnerGuid));
}
/// <summary>Close the trade window — <c>Event_CloseTradeNegotiations
/// @ 0x0056D1E0</c>.</summary>
public void SendCloseTradeNegotiations()
{
uint seq = NextGameActionSequence();
SendGameAction(TradeRequests.BuildCloseTradeNegotiations(seq));
}
/// <summary>Stage an item into the trade — <c>Event_AddToTrade
/// @ 0x0056D0D0</c>.</summary>
public void SendAddToTrade(uint itemGuid, uint tradeSlot = 0u)
{
uint seq = NextGameActionSequence();
SendGameAction(TradeRequests.BuildAddToTrade(seq, itemGuid, tradeSlot));
}
/// <summary>Accept the current offer — <c>Event_AcceptTrade</c> packing
/// <c>Trade::Pack @ 0x005B9FF0</c>'s fixed fields (ACE discards the
/// payload entirely; see TradeRequests.BuildAcceptTrade).</summary>
public void SendAcceptTrade(
uint partnerGuid,
double tradeStamp,
uint tradeStatus,
uint initiatorGuid,
bool initiatorAccepts,
bool partnerAccepts)
{
uint seq = NextGameActionSequence();
SendGameAction(TradeRequests.BuildAcceptTrade(
seq, partnerGuid, tradeStamp, tradeStatus,
initiatorGuid, initiatorAccepts, partnerAccepts));
}
/// <summary>Withdraw a previous accept — <c>Event_DeclineTrade
/// @ 0x0056D270</c>.</summary>
public void SendDeclineTrade()
{
uint seq = NextGameActionSequence();
SendGameAction(TradeRequests.BuildDeclineTrade(seq));
}
/// <summary>Clear the trade window — <c>Event_ResetTrade @ 0x0056D3D0</c>.
/// ACE clears BOTH sides' staged items (lane B §quirks).</summary>
public void SendResetTrade()
{
uint seq = NextGameActionSequence();
SendGameAction(TradeRequests.BuildResetTrade(seq));
}
/// <summary>
/// Send retail GiveObjectRequest (0x00CD). Retail
/// <c>CM_Inventory::Event_GiveObjectRequest @ 0x006ABB00</c> writes