fix(vendor): gate-findings pass — the X button HIDES like retail, clicks return, the dropdown scrolls, pyreal suffix, staged-tab slots
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run
The user's connected gate found five issues; each fixed at the root: G4 (the discovery): retail's vendor X button calls only SetVisible(0) (pc:204147-204182) — the SESSION stays open and re-using the vendor lands on the same-session refresh; the range watcher remains the sole real close. Our port invented a full teardown on X, which is exactly why reopening died. The Runtime fixture proves the wire dispatch was never the problem; ACE has no already-open short-circuit. G3 (regression from the drag-suppression fix): denying IsDragSource also dropped press capture, so clicks fell through to window-drag. UiItemSlot.HandlesClick now claims presses for any occupied cell independent of drag eligibility — clickable and draggable are separate concerns. G5: the authored popup 0x21000043 is ONE scrollable column with a real scrollbar subtree (live-dat scan: ListBox 0x10000350 + scrollbar 0x10000351), not a 3x6 grid. UiMenu gains an authored-driven Scrollable mode (wheel, thumb drag, track paging, up/down buttons); chat's menu is untouched and its ten tests prove it. G1: retail's cost format is "%s %hsp (you have %hsp)" — the p after each %hs is a LITERAL pyreal suffix the port swallowed as part of the specifier. Restored. G2: the Buying/Selling pages' authored lists (same cell template as Items) get the empty-slot fill, presentation-only until staging. Clean-room complete solution: 11,390 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
58c8de264e
commit
5224e43890
8 changed files with 1061 additions and 51 deletions
|
|
@ -248,6 +248,136 @@ public sealed class RuntimeVendorLifecycleTests
|
|||
Assert.Equal(0, snapshot.MaterializedVendorItemCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SecondUse_AfterLocalXClose_StillDispatchesOverTheWireAndReopensOnReapproach()
|
||||
{
|
||||
// G4 (vendor gate finding, client-side half): the user reported that
|
||||
// after X-closing the vendor panel (VendorState.Close() -- a PURE
|
||||
// client-local UI teardown per research doc A.3/A.4, no wire send),
|
||||
// using the vendor again does nothing. This isolates the Runtime
|
||||
// machinery a repeat Use actually depends on --
|
||||
// RuntimeInteractionTransactionState's reservation/busy-count gate
|
||||
// and VendorState's own open/close bookkeeping -- with NO App-layer
|
||||
// world-picking in the loop (AcDream.Runtime.Tests cannot reference
|
||||
// AcDream.App). If this passes, the suppression (if real) is NOT in
|
||||
// Runtime; it would have to be in the App-layer picking/identity
|
||||
// chain (WorldSelectionQuery/SelectionInteractionController), which
|
||||
// needs its own harness to confirm or rule out.
|
||||
using GameRuntime runtime = Create();
|
||||
VendorState vendor = runtime.InventoryOwner.Vendor;
|
||||
using IDisposable wiring = Wire(vendor);
|
||||
RuntimeInteractionTransactionState transactions = runtime.ActionOwner.Transactions;
|
||||
var transport = new FakeTransport();
|
||||
const uint vendorGuid = 0x40001000u;
|
||||
|
||||
// --- First open: press Use -> dispatch -> ApproachVendor arrives ->
|
||||
// UseDone arrives (ACE's Player_Use.TryUseItem ALWAYS schedules
|
||||
// SendUseDoneEvent() after ActOnUse returns, since Vendor.ActOnUse
|
||||
// never sets LastUseTime = float.MinValue -- confirmed against
|
||||
// references/ACE/Source/ACE.Server/WorldObjects/{Vendor,Player_Use}.cs).
|
||||
ItemUseRequestReservation reservation1 =
|
||||
transactions.BeginUseRequestReservation();
|
||||
RuntimeInteractionDispatchResult result1 = transactions.TryDispatchUse(
|
||||
vendorGuid,
|
||||
ownedByPlayer: false,
|
||||
useable: true,
|
||||
reservation1,
|
||||
transport,
|
||||
out _);
|
||||
Assert.Equal(RuntimeInteractionDispatchResult.Dispatched, result1);
|
||||
Assert.Equal(new[] { vendorGuid }, transport.Uses);
|
||||
Assert.Equal(1, transactions.Inventory.BusyCount);
|
||||
|
||||
Dispatch(BuildApproachVendorPayload(
|
||||
vendorGuid: vendorGuid,
|
||||
categories: 0u, minValue: 0u, maxValue: 0u, dealsMagic: false,
|
||||
buyPrice: 1f, sellPrice: 1f, currencyWcid: 0u, currencyAmount: 0u,
|
||||
currencyName: "",
|
||||
items: []));
|
||||
Assert.Equal(vendorGuid, vendor.VendorId);
|
||||
|
||||
transactions.CompleteUse(0u);
|
||||
Assert.Equal(0, transactions.Inventory.BusyCount);
|
||||
|
||||
// --- X-close: the ONLY thing our client does locally.
|
||||
Assert.True(vendor.Close());
|
||||
Assert.Equal(0u, vendor.VendorId);
|
||||
|
||||
// --- Second Use attempt on the SAME vendor guid, well past the
|
||||
// 200ms retail throttle (irrelevant here since TryDispatchUse has no
|
||||
// throttle of its own -- that lives one layer up in
|
||||
// ItemInteractionController/App -- but asserted for clarity).
|
||||
ItemUseRequestReservation reservation2 =
|
||||
transactions.BeginUseRequestReservation();
|
||||
RuntimeInteractionDispatchResult result2 = transactions.TryDispatchUse(
|
||||
vendorGuid,
|
||||
ownedByPlayer: false,
|
||||
useable: true,
|
||||
reservation2,
|
||||
transport,
|
||||
out _);
|
||||
|
||||
// If this fails, RuntimeInteractionTransactionState/VendorState is
|
||||
// the suppressor. If it passes (expected, given VendorState.Close()
|
||||
// touches no interaction-transaction state and ActiveVendorId only
|
||||
// gates USING SHOP ITEMS, not the vendor NPC itself -- see
|
||||
// ItemInteractionPolicy.DecideUse's ContainerId check), the
|
||||
// suppression is NOT here.
|
||||
Assert.Equal(RuntimeInteractionDispatchResult.Dispatched, result2);
|
||||
Assert.Equal(new[] { vendorGuid, vendorGuid }, transport.Uses);
|
||||
Assert.Equal(1, transactions.Inventory.BusyCount);
|
||||
|
||||
// --- Server re-approaches (mirrors Vendor.ActOnUse's UNCONDITIONAL
|
||||
// ApproachVendor -- confirmed no server-side "already open" gate
|
||||
// exists; see the G4 evidence chain in the final report). Our own
|
||||
// VendorState.Apply must reopen from a previous==0 baseline (Close()
|
||||
// already zeroed it), which VendorUiController.OnVendorChanged's
|
||||
// Opened case turns into _window.Show().
|
||||
var kinds = new List<VendorStateTransitionKind>();
|
||||
vendor.Changed += t => kinds.Add(t.Kind);
|
||||
Dispatch(BuildApproachVendorPayload(
|
||||
vendorGuid: vendorGuid,
|
||||
categories: 0u, minValue: 0u, maxValue: 0u, dealsMagic: false,
|
||||
buyPrice: 1f, sellPrice: 1f, currencyWcid: 0u, currencyAmount: 0u,
|
||||
currencyName: "",
|
||||
items: []));
|
||||
|
||||
Assert.Equal(vendorGuid, vendor.VendorId);
|
||||
Assert.Equal([VendorStateTransitionKind.Opened], kinds);
|
||||
|
||||
transactions.CompleteUse(0u);
|
||||
Assert.Equal(0, transactions.Inventory.BusyCount);
|
||||
}
|
||||
|
||||
private sealed class FakeTransport : IRuntimeInteractionTransport
|
||||
{
|
||||
private uint _sequence;
|
||||
public bool IsInWorld { get; set; } = true;
|
||||
public List<uint> Uses { get; } = [];
|
||||
|
||||
public bool TrySendUse(uint serverGuid, out uint sequence)
|
||||
{
|
||||
if (!IsInWorld)
|
||||
{
|
||||
sequence = 0u;
|
||||
return false;
|
||||
}
|
||||
sequence = ++_sequence;
|
||||
Uses.Add(serverGuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySendPickup(
|
||||
uint itemGuid,
|
||||
uint destinationContainerId,
|
||||
int placement,
|
||||
out uint sequence)
|
||||
{
|
||||
sequence = 0u;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VendorId_IsTheLiveActiveVendorIdSeamSource()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue