The adversarial review's three blocking findings, each fixed at root:
1. A vendor session now CLOSES when its entity retires (despawn,
death, ObjectDelete) and at teleport BEGIN
(HasPendingTeleportStart || IsTeleportActive at the existing
per-frame seam — both hosts funnel through
RuntimeWorldTransitState.TryQueueTeleportStart, which flips the
pending flag strictly before activation). The previous permissive
early-return stranded the session forever: panel pinned to a stale
guid, ActiveVendorId swallowing Use for the rest of the session.
2. VendorShopItem carries the desc's stack size, and
VendorPricing.PerUnitValue ports retail's stack-total division
(VendorProfile::VendorSellPrice 0x005D1B00: <= 0 guard, integer
division) — a stack of 50 arrows now prices per arrow, not at 50x.
3. VendorState.Close() guards its observer fanout with the
dispatcher's catch-and-log semantics — a throwing panel listener
can no longer propagate into the unprotected per-frame path.
Register honesty rides along: the 0.6 m UseRadius fallback was
acdream's invention (ACE's CheckClose has no fallback; retail passes
the raw authored radius) — removed, the watcher now uses the raw
radius and AP-160's citations are corrected and extended with the
accepted-position-snapshot cadence; AD-72 files VendorPricing's
double-vs-x87-extended narrowing (AD-33's class, bounded by the
±0.1 margin).
Nine tests added. Clean-room complete solution: 11,311 passed /
4 skipped / 0 failed.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
VendorApproach.TryParse reads the vendor profile and the item list per
the byte-verified field table (research doc §A.2), each item through
the shared PublicWeenieDescParser from 5.0 — zero duplicated parsing.
One wire detail the research table did not spell out, found by
re-reading ACE's writer and confirmed independently in Chorizite's
generated readers: every object body is 4-byte-aligned at its END, so
back-to-back vendor items need an explicit AlignTo4 between entries
(CreateObject never needed it — nothing follows its body). Pinned by a
dedicated test forcing a real 2-byte misalignment via AmmoType.
Stack-size sign extension cross-checked against holtburger.
Six tests: field-order with distinct literals, empty list, and
truncation at each structural boundary — mid-item-tail truncation
deliberately inherits 5.0's established non-throwing partial-item
contract instead of asserting null everywhere.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>