fix(vendor): 6b/6c review corrections — pre-send guards, accumulating staging, trade-note exemption, drag-over tab switch, full-stack sells
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
All thirteen findings, each anchored in recovered bytes or pc reads:
Buy All now runs retail's four PRE-SEND guards in order (pyreal and
alt-currency affordability, container and item slot capacity; strings
recovered from .rdata at 0x007b57b4/0x007b5750) — a rejected batch can
no longer destroy the staged list. Staged adds ACCUMULATE with the
5000 cap ("I can't possibly sell you that much!..." @0x007b59d8) and
the shop rows decrement/restore per RemoveFromShop. The max-value sell
rejection exempts trade notes — the raw bytes at 0x005d1add are `not`
(bitwise), not the pseudo-C's misleading `!`, and the early ret skips
the min check too. BF_RETAINED gates selling end to end (the bit was
already on ClientObject; AP-164's three claims were all false once
traced — RETIRED). Dragging over the vendor window auto-opens the
Selling tab per UpdateDragOver — with a correction to the review's own
citation: token 0x100000cd is the SELLING page, the guard is
"don't reopen the current tab." Sells are full-stack-only (three
retail sites; "Cannot sell part of a stack" @0x007b57ec) and Sell Item
acts on the global selection unconditionally. The confirm string gains
its byte-true trailing '?', dies with the session, staged-row
highlights repaint, dead guids unstage with retail's shopping-list
notice, and move-to-use no longer walks to targets the dispatch would
refuse.
AP-162 narrowed, AP-164 retired, AP-167/AP-168 filed honest.
Clean-room complete solution: 11,508 passed / 4 skipped / 0 failed.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
92ea3977b6
commit
c68ad1e646
11 changed files with 1314 additions and 99 deletions
|
|
@ -18,8 +18,28 @@ public enum VendorSellRejection
|
|||
/// <summary>
|
||||
/// <c>InqAcceptability</c> returned the raw <c>item_types</c> bitmask
|
||||
/// (the type-mismatch/non-sellable-bit branch, <c>pc:005d1af8</c>) —
|
||||
/// the generic case in practice, since a genuine bitmask is almost never
|
||||
/// literally 1-4 (Slice 6b/6c research doc's open question #3).
|
||||
/// mapped here to the generic "You cannot sell that here" message the
|
||||
/// same way retail's <c>DragItemAcceptable</c> switch falls through for
|
||||
/// any return value outside its four named cases (1-4).
|
||||
/// <para>
|
||||
/// F12 (Slice 6b/6c review) — SOFTENED CLAIM: an earlier version of this
|
||||
/// doc comment asserted a genuine bitmask is "almost never literally
|
||||
/// 1-4." That is not true in general: <see cref="ItemType"/>
|
||||
/// <c>MeleeWeapon</c>/<c>Armor</c>/<c>Clothing</c> are the single bits
|
||||
/// <c>1</c>/<c>2</c>/<c>4</c>, and a real specialist vendor's own
|
||||
/// <c>MerchandiseItemTypes</c> could legitimately be exactly one of
|
||||
/// them (a weapon-only, armor-only, or clothing-only shop is an
|
||||
/// ordinary AC vendor archetype). For such a vendor, retail's raw
|
||||
/// <c>item_types</c> return WOULD collide with <c>DragItemAcceptable</c>'s
|
||||
/// own named cases 1/2/4 ("cannot be sold here" / "has no value" /
|
||||
/// "too valuable") — a genuine type mismatch would show the WRONG
|
||||
/// retail message, not the generic one. This class does not reproduce
|
||||
/// that collision (it always returns the semantic
|
||||
/// <see cref="WrongType"/> case, never a raw integer another case could
|
||||
/// alias), so acdream's own message is unaffected either way; this note
|
||||
/// only corrects the doc's claim about how often retail's OWN collision
|
||||
/// is reachable, in case a byte-exact reproduction is ever wanted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
WrongType,
|
||||
|
||||
|
|
@ -40,6 +60,19 @@ public enum VendorSellRejection
|
|||
/// <summary>
|
||||
/// <c>InqAcceptability</c>'s "too valuable" branch (<c>pc:005d1add</c>):
|
||||
/// <c>max_value != -1 && value > max_value</c>.
|
||||
/// <para>
|
||||
/// F3 (Slice 6b/6c review, byte-verified at <c>0x005d1add</c>): the
|
||||
/// actual x86 at that return site is <c>mov eax,edi; shr eax,0x10;
|
||||
/// not eax; and eax,4; ret</c> — i.e. <c>(~(itemTypeMask >> 16)) & 4</c>,
|
||||
/// a BITWISE complement (the decompiled pseudo-C's <c>!</c> is
|
||||
/// misleading — it is not a logical NOT). <c>ItemType.PromissoryNote</c>
|
||||
/// (<c>0x00040000</c>, bit 18) sits exactly at bit 2 of
|
||||
/// <c>itemTypeMask >> 16</c>, so a trade note above the vendor's
|
||||
/// max value returns 0 (fully <see cref="None"/>, exempt) instead of 4
|
||||
/// (<see cref="TooValuable"/>) — and the <c>ret</c> at that exact
|
||||
/// address means the min-value check below is skipped entirely for a
|
||||
/// trade note, not merely the max-value rejection.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
TooValuable,
|
||||
|
||||
|
|
@ -84,6 +117,18 @@ public static class VendorSellAcceptability
|
|||
/// The vendor's <c>VendorShopProfile.MerchandiseMaxValue</c> —
|
||||
/// <see cref="NoLimit"/> means retail's unset <c>-1</c>.
|
||||
/// </param>
|
||||
/// <param name="publicWeenieBitfield">
|
||||
/// F4 (Slice 6b/6c review): the dragged item's own
|
||||
/// <c>PublicWeenieDesc::_bitfield</c> (<see cref="ClientObject.PublicWeenieBitfield"/>,
|
||||
/// populated on every ordinary <c>CreateObject</c> — including the
|
||||
/// player's own pack items, the only things ever dragged here). Tested
|
||||
/// against <see cref="PublicWeenieFlags.Retained"/>
|
||||
/// (<c>BF_RETAINED = 0x01000000</c>, acclient.h:6456) — retail's
|
||||
/// <c>InqAcceptability</c> ORs this bit into the SAME type-mismatch
|
||||
/// branch (byte 3 bit 0 of the bitfield, <c>pc:005d1aa7</c>), so it
|
||||
/// folds into the same <see cref="VendorSellRejection.WrongType"/>
|
||||
/// outcome, not a distinct rejection reason.
|
||||
/// </param>
|
||||
public static VendorSellRejection Evaluate(
|
||||
bool ownedByPlayer,
|
||||
int containedItemCount,
|
||||
|
|
@ -91,21 +136,34 @@ public static class VendorSellAcceptability
|
|||
int perUnitValue,
|
||||
uint merchandiseItemTypes,
|
||||
uint merchandiseMinValue,
|
||||
uint merchandiseMaxValue)
|
||||
uint merchandiseMaxValue,
|
||||
uint publicWeenieBitfield = 0u)
|
||||
{
|
||||
if (!ownedByPlayer)
|
||||
return VendorSellRejection.NotOwnedByPlayer;
|
||||
if (containedItemCount > 0)
|
||||
return VendorSellRejection.None;
|
||||
|
||||
if ((itemTypeMask & merchandiseItemTypes) == 0u)
|
||||
// F4: InqAcceptability's first check ORs the type-mask mismatch
|
||||
// with the BF_RETAINED bit (pc:005d1aa7) -- both branches return
|
||||
// the SAME raw item_types value, so both fold into WrongType here.
|
||||
bool retained = (publicWeenieBitfield & (uint)PublicWeenieFlags.Retained) != 0u;
|
||||
if ((itemTypeMask & merchandiseItemTypes) == 0u || retained)
|
||||
return VendorSellRejection.WrongType;
|
||||
|
||||
if (perUnitValue == 0)
|
||||
return VendorSellRejection.NoValue;
|
||||
|
||||
if (merchandiseMaxValue != NoLimit && perUnitValue > merchandiseMaxValue)
|
||||
return VendorSellRejection.TooValuable;
|
||||
{
|
||||
// F3 (byte-verified at 0x005d1add) -- see TooValuable's own doc
|
||||
// comment: a PromissoryNote (trade note) is EXEMPT from the
|
||||
// max-value rejection (and, by the disassembly's early ret,
|
||||
// from the min-value check too), not just capped differently.
|
||||
return (itemTypeMask & (uint)ItemType.PromissoryNote) != 0u
|
||||
? VendorSellRejection.None
|
||||
: VendorSellRejection.TooValuable;
|
||||
}
|
||||
|
||||
if (merchandiseMinValue != NoLimit && perUnitValue < merchandiseMinValue)
|
||||
return VendorSellRejection.TooCheap;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue