fix: trade gate round 1 - the Use-on-player route, authored grid cells,

and [trade] seam probes

- Use on a selected player did nothing: the world Use path
  (SelectionInteractionController.RequestUse) dispatches 0x0036 directly
  and NEVER consults DetermineUseResult - lane C's "both paths already
  classified" claim described the POLICY's capability, not a live
  caller (the verify-subagent-claims lesson, again). Retail's
  CPlayerSystem::UsingItem @0x00562F70 consults it and routes result 5
  (another PLAYER) to AttemptToOpenTradeNegotiations @0x0056DEE0, peace
  mode only, with no Use send. Ported as
  ItemInteractionController.TryOpenSecureTradeWithPlayer, called at
  RequestUse entry.
- The broken window: both grids rendered their raw authored strip art
  with no cell layout (the gold-ring tile was the naked track). They now
  get the vendor strips' exact single-row 32px config + the authored
  empty-slot art resolved through ItemListCellTemplate (cell-template
  attr 0x1000000E -> 0x1000033A).
- Drag-onto-player "nothing happens": the policy chain reads correct
  end-to-end in code (pick -> PlaceIn3D -> StartSecureTrade -> event ->
  cmd), so TEMPORARY [trade] probe lines now bracket every seam
  (drag-release pick, policy arm, controller request, use-on-player) -
  the next gate log pinpoints the break if it persists. Stripped once
  the two-client gate passes.

App suite 4,990/3 skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-14 12:03:22 +02:00
parent 067cbea8a5
commit 22f30f291d
4 changed files with 103 additions and 2 deletions

View file

@ -1033,6 +1033,41 @@ public sealed class ItemInteractionController : IDisposable
ClearTargetMode();
}
/// <summary>
/// Gate fix (2026-08-14 trade round 1): the world Use path
/// (<c>SelectionInteractionController.RequestUse</c>) dispatches
/// <c>0x0036 Use</c> directly and never consults
/// <c>DetermineUseResult</c> — retail's <c>CPlayerSystem::UsingItem
/// @ 0x00562F70</c> DOES, and routes result 5 (target is another
/// PLAYER) to <c>ClientTradeSystem::AttemptToOpenTradeNegotiations
/// @ 0x0056DEE0</c>, gated on peace mode, with no Use send at all.
/// Returns true when the target is another player (the use is consumed
/// by the trade path either way; in combat mode retail's open attempt
/// early-outs silently).
/// </summary>
public bool TryOpenSecureTradeWithPlayer(uint targetGuid)
{
if (targetGuid == 0u || targetGuid == _playerGuid())
return false;
ClientObject? target = _objects.Get(targetGuid);
if (target is null
|| ((PublicWeenieFlags)(target.PublicWeenieBitfield ?? 0u)
& PublicWeenieFlags.Player) == 0)
return false;
if (PhysicsDiagnosticsTradeProbe)
Console.WriteLine(
$"[trade] use-on-player guid=0x{targetGuid:X8} "
+ $"nonCombat={_inNonCombatMode()} subscribers={SecureTradeRequested is not null}");
if (_inNonCombatMode())
SecureTradeRequested?.Invoke(targetGuid, 0u);
return true;
}
/// <summary>TEMPORARY (2026-08-14 trade gate round 1): [trade] seam
/// probe lines, stripped once the two-client gate passes.</summary>
internal static bool PhysicsDiagnosticsTradeProbe = true;
public bool DropToWorld(ItemDragPayload payload)
=> PlaceIn3D(payload, targetGuid: 0u);
@ -1184,6 +1219,10 @@ public sealed class ItemInteractionController : IDisposable
// (ItemHolder::AttemptPlaceIn3D @ 0x00588600's option branch
// → ClientTradeSystem::AttemptToTradeItem @ 0x0056DF80).
// ObjectId = the dragged item, TargetId = the player.
if (PhysicsDiagnosticsTradeProbe)
Console.WriteLine(
$"[trade] drag-on-player item=0x{action.ObjectId:X8} "
+ $"target=0x{action.TargetId:X8} subscribers={SecureTradeRequested is not null}");
SecureTradeRequested?.Invoke(action.TargetId, action.ObjectId);
break;
case ItemPolicyActionKind.DropToWorld: