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

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:
Erik 2026-08-08 15:23:10 +02:00
parent 68568a3a59
commit d003449bb4
11 changed files with 680 additions and 103 deletions

View file

@ -42,4 +42,37 @@ public static class VendorSplitPolicy
IsSplitExempt(itemType)
? 1
: authoredStackSize is { } size && size > 0 ? size : 1;
/// <summary>
/// Grand-gate finding R1 (2026-08-08, register AP-169 correction): the
/// retail-faithful "how big is one stack of this item" answer for a
/// VENDOR-owned selection, used everywhere retail reads
/// <c>PublicWeenieDesc::_stackSize</c> for splitting/seeding purposes
/// (<c>gmToolbarUI::HandleSelectionChanged</c>,
/// <c>pc:198688</c>/<c>198744</c>/<c>198774</c>/<c>198791</c>;
/// <c>ItemHolder::GetObjectSplitSize</c>, <c>pc:401465-401477</c>).
///
/// <para>
/// Prefers <paramref name="descStackSize"/> (the wire's own
/// <c>PublicWeenieDesc::_stackSize</c> — retail-faithful FIRST, honored
/// unchanged if a real retail server or a future ACE fix ever populates
/// it). ACE never sets it for a vendor browse-list row
/// (<c>Vendor.LoadInventoryItem</c> never calls <c>wo.SetStackSize</c>),
/// so this falls back to <paramref name="maxStackSize"/> — the item
/// TYPE's authored stack ceiling, which ACE DOES populate (an ordinary
/// weenie property, not an instance-specific stack count) and which a
/// real retail server evidently uses AS <c>_stackSize</c> for an
/// unlimited-supply "one full stack" browse listing (the live retail
/// screenshot: a Prismatic Taper listing shows "1000 Prismatic Tapers",
/// 1000 being the taper's authored max stack size, not any bounded
/// supply count). Finally falls back to 1 (non-splittable) when neither
/// field is available. See the register, AP-169.
/// </para>
/// </summary>
public static int ResolveAuthoredStackSize(int? descStackSize, int? maxStackSize) =>
descStackSize is { } desc && desc > 0
? desc
: maxStackSize is { } max && max > 0
? max
: 1;
}

View file

@ -58,6 +58,23 @@ public readonly record struct VendorShopItem(
// matching retail's own zeroed-struct default of 0 for the same case
// (see VendorPricing.PerUnitValue's <= 0 guard).
int? DescStackSize = null,
// Grand-gate finding R1 (2026-08-08, register AP-169 correction): the
// item TYPE's authored stack ceiling (retail PublicWeenieDesc::
// _maxStackSize, wire AcDream.Core.Net.Messages.PublicWeenieDescBody.
// StackSizeMax). Retail's OWN InqListSlotCount (pc:200038-200065) and
// the Buying tab's Buy Item/Clear Item removal rule
// (gmVendorUI::HandleButtonClicks cases 0x100000c9/0x100000cb,
// pc:203989-204010/204080-204094) read THIS field literally, never
// DescStackSize, to decide whether an item is stackable at all. It also
// serves as this port's retail-faithful SUBSTITUTE for the toolbar's
// splitSize/maxSplitSize seed (gmToolbarUI::HandleSelectionChanged,
// pc:198688/198744/198774/198791 — reads PublicWeenieDesc::_stackSize,
// which ACE never populates for a vendor browse-list row; a real retail
// server evidently authors _stackSize == _maxStackSize for an
// unlimited-supply "one full stack" browse listing, so MaxStackSize is
// the value retail's own server would have put there) — see
// VendorSplitPolicy.ResolveAuthoredStackSize and the register (AP-169).
int? MaxStackSize = null,
// Review finding F5 (Slice 5.4 review): PublicWeenieDescBody already
// carries these three (IconOverlayId/IconUnderlayId/UiEffects) — see
// AcDream.Core.Net.Messages.PublicWeenieDescBody. Mirrors