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

@ -226,9 +226,9 @@ public sealed class VendorShopItemMaterializer : IDisposable
/// the wire-shaped merge patch <see cref="ClientObjectTable.Ingest"/>
/// expects.
/// <para>
/// G2 gate-finding fix (2026-08-08, register AP-169): retail's own
/// client (<c>gmToolbarUI::HandleSelectionChanged</c>, <c>pc:198688</c>/
/// <c>198744</c>/<c>198774</c>/<c>198791</c>) reads
/// R1 gate-finding fix (2026-08-08, register AP-169 correction): retail's
/// own client (<c>gmToolbarUI::HandleSelectionChanged</c>,
/// <c>pc:198688</c>/<c>198744</c>/<c>198774</c>/<c>198791</c>) reads
/// <c>eax_5-&gt;pwd._stackSize</c> — our <see cref="VendorShopItem.DescStackSize"/>
/// — uniformly for BOTH owned-inventory and vendor-owned selections to
/// decide whether the toolbar split slider shows and what it caps at.
@ -246,18 +246,28 @@ public sealed class VendorShopItemMaterializer : IDisposable
/// for every vendor listing. Reading only <c>DescStackSize</c> here
/// therefore left <see cref="ClientObject.StackSize"/> at its default
/// for every materialized shop item, so the toolbar split slider never
/// appeared for ANY vendor stack, matching the live report exactly (it
/// DID appear for owned-inventory stacks, which ACE populates normally
/// via ordinary pickup/loot <c>SetStackSize</c> calls). This now prefers
/// <c>DescStackSize</c> when present (retail-faithful first, and
/// forward-compatible with any server that DOES populate it), falling
/// back to the packed <c>StackSize</c> supply-count field clamped to a
/// sane positive bound — the field that IS reliably populated against
/// ACE. The <c>StackSize == -1</c> (unlimited-supply) sentinel has no
/// bounded per-row purchase cap in <see cref="VendorShopItem"/>'s wire
/// shape today (no <c>_maxStackSize</c> field carried), so it falls
/// through to the conservative "1" default rather than inventing an
/// arbitrary ceiling — see the register.
/// appeared for ANY vendor stack.
/// </para>
/// <para>
/// <b>The G2 packed-supply-count fallback (retired 2026-08-08).</b> An
/// earlier fix fell back to <see cref="VendorShopItem.StackSize"/> (the
/// packed ItemProfile "how many for sale" dword) when
/// <c>DescStackSize</c> was absent. That did not survive live testing —
/// a standard vendor listing (e.g. a Prismatic Taper) has UNLIMITED
/// stock (<c>StackSize == -1</c>), so the fallback produced nothing
/// usable and the bar stayed hidden, matching the live report exactly
/// (bare "Prismatic Taper", no bar, no count). The live retail
/// screenshot showed "1000 Prismatic Tapers" with the bar visible and a
/// ceiling of 1000 — 1000 being the taper's authored MAX STACK SIZE, not
/// any bounded supply count. This now prefers <c>DescStackSize</c> when
/// present (retail-faithful first, forward-compatible with any server
/// that DOES populate it), falling back to
/// <see cref="VendorShopItem.MaxStackSize"/> — the item TYPE's authored
/// stack ceiling, which ACE DOES populate (an ordinary weenie property)
/// and which a real retail server evidently uses AS <c>_stackSize</c>
/// for an unlimited-supply "one full stack" browse listing. See
/// <see cref="VendorSplitPolicy.ResolveAuthoredStackSize"/> and the
/// register, AP-169.
/// </para>
/// <para>
/// Every other field <see cref="VendorShopItem"/> doesn't carry
@ -276,8 +286,8 @@ public sealed class VendorShopItemMaterializer : IDisposable
IconUnderlayId: item.IconUnderlayId,
Effects: item.Effects,
Value: item.Value,
StackSize: ResolveDisplayStackSize(item),
StackSizeMax: null,
StackSize: VendorSplitPolicy.ResolveAuthoredStackSize(item.DescStackSize, item.MaxStackSize),
StackSizeMax: item.MaxStackSize,
Burden: null,
ContainerId: vendorId,
WielderId: 0u,
@ -291,23 +301,6 @@ public sealed class VendorShopItemMaterializer : IDisposable
Workmanship: null,
PluralName: item.PluralName);
/// <summary>
/// G2 fix: <see cref="VendorShopItem.DescStackSize"/> when the wire
/// actually carried it (nonzero — a genuinely retail-faithful server),
/// else the packed <see cref="VendorShopItem.StackSize"/> supply count
/// (what ACE reliably sends) when it names a real bounded quantity,
/// else 1 (non-splittable — the safe default for the unlimited-supply
/// sentinel or a genuinely single-unit listing).
/// </summary>
private static int? ResolveDisplayStackSize(VendorShopItem item)
{
if (item.DescStackSize is { } desc && desc > 0)
return desc;
if (item.StackSize > 0)
return item.StackSize;
return 1;
}
public void Dispose()
{
if (_disposed) return;