fix: trade gate round 3 - the mount-time-captured dead command bus (ONE

root cause for every dead interaction) + retail's Total Items caption

The round-2 probes nailed it: the request seam fired for BOTH open
paths (use AND drag - "drag-release pick" -> "drag-on-player" ->
"request"), but no open-cmd, no wire-open, and no LiveCommandBus
drop-warning ever printed. MountSecureTrade captured
_bindings.Options.CommandBus() ONCE at mount time - the pre-session
surface whose Publish routes into a null route silently. CommandBus is
a Func for exactly this reason; the social mounts resolve it inside
each lambda. Every trade command - open (use + drag), accept (the
"unpressable" Trade button - the click FIRED, the publish died),
Clear All, close, and drop-on-grid staging - died on that one captured
bus. All six lambdas now resolve the Func per call.

Also: ID_SecureTrade_TotalItemsLabel probe-verified token-free
(fragments ["Total Items: ", ""], one ITEMS variable 0x004E8A23) and
composed via ResolveTemplate - the count texts read retail's exact
"Total Items: N". AD-95 RETIRED same-day.

The pre-feature stub-toast test row (drag-on-player option-on expecting
"Secure trade is not open.") now pins the SecureTradeRequested seam
instead. App suite 4,991/3 skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-14 12:34:31 +02:00
parent f2144ab2a4
commit ffc73f80bf
6 changed files with 97 additions and 16 deletions

View file

@ -610,6 +610,12 @@ public sealed class LiveSessionCommandRouterTests
RemoveFriend: _ => { },
ClearFriends: () => { },
RequestLegacyFriends: () => { },
OpenTradeNegotiations: _ => { },
CloseTradeNegotiations: () => { },
AddToTrade: _ => { },
AcceptTrade: (_, _, _) => { },
DeclineTrade: () => { },
ResetTrade: () => { },
ModifyCharacterSquelch: (_, _, _, _) => { },
ModifyAccountSquelch: (_, _) => { },
ModifyGlobalSquelch: (_, _) => { },

View file

@ -1540,15 +1540,25 @@ public sealed class ItemInteractionControllerTests
var payload = new ItemDragPayload(
item, ItemDragSource.Inventory, SourceSlot: 0, SourceCell: new UiItemSlot());
var tradeRequests = new List<(uint Partner, uint Item)>();
h.Controller.SecureTradeRequested += (partner, dragged) =>
tradeRequests.Add((partner, dragged));
bool result = h.Controller.PlaceIn3D(payload, targetPlayer);
Assert.Equal(sendsGive, result);
if (sendsGive)
{
Assert.Equal(new[] { (targetPlayer, item, 1u) }, h.Gives);
Assert.Empty(tradeRequests);
}
else
{
// Trade feature (2026-08-14): the option-on drag now raises the
// SecureTradeRequested seam (retail's AttemptToTradeItem
// @ 0x0056DF80) instead of the pre-feature stub toast.
Assert.Empty(h.Gives);
Assert.Contains(h.Toasts, text => text.Contains("Secure trade", StringComparison.Ordinal));
Assert.Equal([(targetPlayer, item)], tradeRequests);
}
Assert.Equal(Pack, h.Objects.Get(item)!.ContainerId);
}

View file

@ -103,6 +103,44 @@ public sealed class PowerbarLayoutProbeTests
Console.WriteLine($"[pbprobe] scanned {scanned} layouts");
}
/// <summary>2026-08-14 trade gate round 3: dump
/// ID_SecureTrade_TotalItemsLabel's fragments + variable hashes so the
/// count text can use ResolveTemplate if token-free (AD-95).</summary>
[Fact]
public void ProbeTotalItemsTemplate()
{
if (Environment.GetEnvironmentVariable("ACDREAM_PROBE_POWERBAR") != "1")
return;
var datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
?? Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"Documents",
"Asheron's Call");
using var dats = new DatCollection(datDir, DatAccessType.Read);
uint hash = DatStringResolver.ComputeHash("ID_SecureTrade_TotalItemsLabel");
var table = dats.Get<DatReaderWriter.DBObjs.StringTable>(0x23000001u);
Console.WriteLine($"[pbprobe] key hash=0x{hash:X8} tableFound={table is not null}");
if (table is not null && table.Strings.TryGetValue(hash, out var entry))
{
for (int i = 0; i < entry.Strings.Count; i++)
Console.WriteLine($"[pbprobe] fragment[{i}]='{entry.Strings[i].Value}'");
for (int i = 0; i < entry.Variables.Count; i++)
Console.WriteLine($"[pbprobe] variable[{i}]=0x{entry.Variables[i]:X8}");
foreach (string candidate in new[]
{ "COUNT", "NUM", "NUMBER", "ITEMS", "TOTAL", "AMOUNT", "N" })
{
Console.WriteLine(
$"[pbprobe] hash('{candidate}')=0x{DatStringResolver.ComputeHash(candidate):X8}");
}
}
else
{
Console.WriteLine("[pbprobe] entry NOT FOUND in 0x23000001");
}
}
private static ElementInfo? FindById(ElementInfo element, uint id)
{
if (element.Id == id) return element;