fix(vendor): re-gate residuals — MaxStackSize is the stack operand, wire-authored use radius, purse summaries
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
R1 the split bar's operand is the item's authored MaxStackSize —
three retail sites read pwd._maxStackSize directly (InqListSlotCount
pc:200052, buy-button cases pc:203996/204086) where ACE never fills
the desc stack and standard stock is unlimited. Threaded StackSizeMax
end to end with one shared resolver; the two literal _maxStackSize
sites are now byte-exact; AP-165 retired, AP-169 corrected.
R2 walk-to-vendor never opened because GetUseRadius used an UNCITED
3m Creature heuristic as the local stop distance while ACE's poll
demands the authored radius (default 0.6 m) — the walk stopped and
the Use fired far outside acceptance. Now reads the wire-authored
spawn UseRadius with ACE's exact fallback; heuristic constants
deleted. A first sabotage attempt was non-discriminating
(coincidental 0.6) and was corrected — the discriminating version is
what landed.
R3 the Buying/Selling purse summaries ("Buying %d %s worth %hsp" /
"You have %hsp") recovered from the binary data segment where BN
mis-attributes the Buy-side literal; wired to staging and money
changes on the four authored text elements; AP-166 narrowed to the
pending-sell highlight.
Clean-room complete solution: 11,528 passed / 4 skipped / 0 failed.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
68568a3a59
commit
d003449bb4
11 changed files with 680 additions and 103 deletions
|
|
@ -188,6 +188,11 @@ public sealed class VendorUiControllerTests
|
|||
public readonly UiButton BuyAllButton;
|
||||
public readonly UiButton BuyClearItemButton;
|
||||
public readonly UiButton BuyClearListButton;
|
||||
// R3: the Buying/Selling tabs' own staged summary text.
|
||||
public readonly UiText BuyListText = new();
|
||||
public readonly UiText BuyPurseText = new();
|
||||
public readonly UiText SellListText = new();
|
||||
public readonly UiText SellPurseText = new();
|
||||
// Slice 6c: "Selling" tab staging widgets.
|
||||
public readonly UiItemList SellingList = new();
|
||||
public readonly UiButton SellItemButton;
|
||||
|
|
@ -280,11 +285,15 @@ public sealed class VendorUiControllerTests
|
|||
BuyingPage.AddChild(BuyAllButton);
|
||||
BuyingPage.AddChild(BuyClearItemButton);
|
||||
BuyingPage.AddChild(BuyClearListButton);
|
||||
BuyingPage.AddChild(BuyListText);
|
||||
BuyingPage.AddChild(BuyPurseText);
|
||||
SellingPage.AddChild(SellingList);
|
||||
SellingPage.AddChild(SellItemButton);
|
||||
SellingPage.AddChild(SellAllButton);
|
||||
SellingPage.AddChild(SellClearItemButton);
|
||||
SellingPage.AddChild(SellClearListButton);
|
||||
SellingPage.AddChild(SellListText);
|
||||
SellingPage.AddChild(SellPurseText);
|
||||
|
||||
var layout = new ImportedLayout(root, new Dictionary<uint, UiElement>
|
||||
{
|
||||
|
|
@ -312,6 +321,10 @@ public sealed class VendorUiControllerTests
|
|||
[VendorUiController.SellAllButtonId] = SellAllButton,
|
||||
[VendorUiController.SellClearItemButtonId] = SellClearItemButton,
|
||||
[VendorUiController.SellClearListButtonId] = SellClearListButton,
|
||||
[VendorUiController.BuyingListTextId] = BuyListText,
|
||||
[VendorUiController.BuyingPurseTextId] = BuyPurseText,
|
||||
[VendorUiController.SellingListTextId] = SellListText,
|
||||
[VendorUiController.SellingPurseTextId] = SellPurseText,
|
||||
});
|
||||
|
||||
Window = RetailWindowFrame.Mount(
|
||||
|
|
@ -1199,6 +1212,36 @@ public sealed class VendorUiControllerTests
|
|||
Assert.Equal(25, h.Buys.Single().Amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R1 gate-finding fix, task item 5 (2026-08-08): an UNLIMITED-STOCK
|
||||
/// item with no wire <c>DescStackSize</c> (the real ACE shape for a
|
||||
/// browse-list row — see <see cref="VendorSplitPolicy.ResolveAuthoredStackSize"/>'s
|
||||
/// doc comment) but a real <see cref="VendorShopItem.MaxStackSize"/>
|
||||
/// (1000, matching the live retail screenshot's Prismatic Taper) must
|
||||
/// still let the player buy an arbitrary quantity up to that ceiling —
|
||||
/// the slider isn't merely decorative. This harness doesn't mount
|
||||
/// <c>SelectedObjectController</c> (production's toolbar owns split
|
||||
/// seeding), so it stands in for "the toolbar showed a ceiling of 1000
|
||||
/// (MaxStackSize) and the player dragged to 300" by seeding
|
||||
/// <see cref="Harness.SplitQuantity"/> directly with that SAME ceiling.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BuyButton_Press_UnlimitedStockItemUsingMaxStackSizeCeiling_SendsTheSliderQuantity()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.State.Apply(VendorGuid, Profile(), new[]
|
||||
{
|
||||
new VendorShopItem(
|
||||
StackedItemGuid, -1, 3u, "Prismatic Taper", (uint)ItemType.SpellComponents, 300u, 1000,
|
||||
DescStackSize: null, MaxStackSize: 1000),
|
||||
});
|
||||
h.SplitQuantity.Reset(1000u, initialValue: 300u);
|
||||
|
||||
h.BuyButton.OnClick!.Invoke();
|
||||
|
||||
Assert.Equal(300, h.Buys.Single().Amount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuyButton_Press_NonStackedItem_IgnoresAStaleSliderFromAPreviouslySelectedStackableItem()
|
||||
{
|
||||
|
|
@ -1443,7 +1486,7 @@ public sealed class VendorUiControllerTests
|
|||
{
|
||||
new VendorShopItem(
|
||||
StackedItemGuid, -1, 3u, "Arrows", (uint)ItemType.MissileWeapon, 300u, 1000,
|
||||
DescStackSize: 100),
|
||||
DescStackSize: 100, MaxStackSize: 100),
|
||||
});
|
||||
h.SplitQuantity.Reset(5000u, initialValue: 4990u);
|
||||
h.AddButton.OnClick!.Invoke();
|
||||
|
|
@ -2477,4 +2520,109 @@ public sealed class VendorUiControllerTests
|
|||
|
||||
Assert.Equal(1, h.BuyingList.GetNumUIItems());
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// R3 — the Buying/Selling tabs' own staged-count/total-value/purse text
|
||||
// (grand-gate live evidence: retail's Buying tab shows "Buying 2 items
|
||||
// worth 422p" / "You have 23p"; the strings are recovered byte-verbatim
|
||||
// — see VendorUiController.BuildTransactionListText/BuildPurseText).
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
public void BuyingTabSummaryText_TracksStagedCountValueAndPlayerPurse()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.State.Apply(VendorGuid, Profile(), new[]
|
||||
{
|
||||
new VendorShopItem(ArmorItemGuid, -1, 2u, "Chainmail", (uint)ItemType.Armor, 200u, 500),
|
||||
});
|
||||
|
||||
// Before staging: zero staged count/value, still grammatically
|
||||
// plural ("0 items"), purse text is live regardless of staging.
|
||||
Assert.Equal("Buying 0 items worth 0p", GetText(h.BuyListText));
|
||||
Assert.Equal($"You have {Harness.DefaultPlayerCoinValue:N0}p", GetText(h.BuyPurseText));
|
||||
|
||||
h.AddButton.OnClick!.Invoke();
|
||||
|
||||
// ComputeBuyTransactionValue: sellRate 1.5 * value 500 * quantity 1
|
||||
// = 750, ceil(750 - 0.1) = 750.
|
||||
Assert.Equal("Buying 1 item worth 750p", GetText(h.BuyListText));
|
||||
Assert.Equal($"You have {Harness.DefaultPlayerCoinValue:N0}p", GetText(h.BuyPurseText));
|
||||
|
||||
h.BuyClearListButton.OnClick!.Invoke();
|
||||
|
||||
Assert.Equal("Buying 0 items worth 0p", GetText(h.BuyListText));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R3: a money change with no staging change at all must still repaint
|
||||
/// the purse line — retail's own purse text always reads the LIVE
|
||||
/// holding, with no "did staging change" gate (see
|
||||
/// <c>VendorUiController.OnObjectMoneyChanged</c>'s doc comment).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BuyingTabPurseText_UpdatesOnAPlayerMoneyChangeAloneWithNoStagingChange()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.State.Apply(VendorGuid, Profile(), Array.Empty<VendorShopItem>());
|
||||
Assert.Equal($"You have {Harness.DefaultPlayerCoinValue:N0}p", GetText(h.BuyPurseText));
|
||||
|
||||
var bundle = new PropertyBundle();
|
||||
bundle.Ints[(uint)PropertyInt.CoinValue] = 42;
|
||||
h.Objects.UpsertProperties(Harness.PlayerGuid, bundle);
|
||||
|
||||
Assert.Equal("You have 42p", GetText(h.BuyPurseText));
|
||||
// Selling's purse text shares the SAME live holding read.
|
||||
Assert.Equal("You have 42p", GetText(h.SellPurseText));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SellingTabSummaryText_TracksStagedCountValueAndPlayerPurse()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.State.Apply(VendorGuid, SellProfile((uint)ItemType.Armor), Array.Empty<VendorShopItem>());
|
||||
MakePlayerOwned(h, PlayerOwnedArmorGuid, ItemType.Armor, 100);
|
||||
|
||||
Assert.Equal("Selling 0 items worth 0p", GetText(h.SellListText));
|
||||
Assert.Equal($"You have {Harness.DefaultPlayerCoinValue:N0}p", GetText(h.SellPurseText));
|
||||
|
||||
h.Controller.HandleDropRelease(
|
||||
h.SellingList, new UiItemSlot(), DragFromInventory(PlayerOwnedArmorGuid));
|
||||
|
||||
// ComputeSellTransactionValue: SellProfile's BuyPrice 1.0 * value
|
||||
// 100 * quantity 1 = 100, floor(100 + 0.1) = 100.
|
||||
Assert.Equal("Selling 1 item worth 100p", GetText(h.SellListText));
|
||||
Assert.Equal($"You have {Harness.DefaultPlayerCoinValue:N0}p", GetText(h.SellPurseText));
|
||||
|
||||
h.SellClearListButton.OnClick!.Invoke();
|
||||
|
||||
Assert.Equal("Selling 0 items worth 0p", GetText(h.SellListText));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Two staged units of the SAME stackable row: the count is the STAGED
|
||||
/// QUANTITY total (2), not the row count (1) — matching the live
|
||||
/// evidence screenshot's "Buying 2 items worth 422p" exactly (a single
|
||||
/// stacked row, quantity 2).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void BuyingTabSummaryText_CountsStagedQuantityNotRowCountForAStackedItem()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.State.Apply(VendorGuid, Profile(), new[]
|
||||
{
|
||||
new VendorShopItem(
|
||||
StackedItemGuid, -1, 3u, "Prismatic Taper", (uint)ItemType.SpellComponents, 300u, 1000,
|
||||
DescStackSize: 100),
|
||||
});
|
||||
h.SplitQuantity.Reset(100u, initialValue: 2u);
|
||||
|
||||
h.AddButton.OnClick!.Invoke();
|
||||
|
||||
Assert.Equal(1, h.BuyingList.GetNumUIItems());
|
||||
// ComputeBuyTransactionValue: perUnit = value 1000 / DescStackSize
|
||||
// 100 = 10; sellRate 1.5 * perUnit 10 * quantity 2 = 30,
|
||||
// ceil(30 - 0.1) = 30.
|
||||
Assert.Equal("Buying 2 items worth 30p", GetText(h.BuyListText));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue