fix(vendor): grand-gate findings — wire-truth container counts, the live split bar, arrival-gated use, prepend-order race
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
Four live findings, each with the paper-verification failure named: G1 the container-capacity guard counted containers by a local type/capacity heuristic that over-classifies ordinary items; retail buckets from the wire's ContainerProperties at insert. Now reads ClientObjectTable's existing ContainerTypeHint (AP-168 narrowed to the shop-stock half; a pre-check must never false-block). G2 the amount bar never showed live because ACE never sets StackSize on browse listings — DescStackSize is null for every real vendor item and the C4 paper test hand-set the field, bypassing the materializer. The materializer now falls back to the packed supply count (AP-169, ACE adaptation); the new test drives the REAL materializer. G3 an out-of-range Use now dispatches ON ARRIVAL (pickup's shape): ACE's HandleActionUseItem only opens the vendor when the Use finds the player in range — a click-time send is greeted and dropped (AP-170, ACE adaptation; retail's server walks the player, ACE does not). G4 bought items appended because ACE's placement echo (UIQueue) can beat the CreateObject (SmartboxQueue) — cross-queue, no ordering guarantee — and the early echo was silently dropped. ClientObjectTable now stashes unresolved placements and replays them at Ingest: buys land at the retail list head. No register row — this RESTORES parity. Clean-room complete solution: 11,521 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c68ad1e646
commit
68568a3a59
12 changed files with 1140 additions and 90 deletions
|
|
@ -457,17 +457,25 @@ public sealed class SelectionInteractionControllerTests
|
|||
|
||||
/// <summary>
|
||||
/// C1 (Slice 6b move-to-use, docs/research/2026-08-08-slice6b-vendor-
|
||||
/// completion-research.md Q2): an out-of-range Use kicks off the SAME
|
||||
/// local client-predicted MoveToObject approach Pickup's far-range
|
||||
/// branch already installs, giving the walk immediate visual feel. The
|
||||
/// wire send is never gated on arrival — retail's
|
||||
/// <c>ItemHolder::UseObject @ 0x00588A80</c> has no range check and
|
||||
/// sends unconditionally, so the dispatch and the approach both happen
|
||||
/// at click time, in that order. A later natural MoveTo completion must
|
||||
/// not re-dispatch (Use has no post-arrival token the way Pickup does).
|
||||
/// completion-research.md Q2, REVISED for G3 — grand-gate finding
|
||||
/// 2026-08-08, register AP-170): an out-of-range Use kicks off the SAME
|
||||
/// local client-predicted MoveToObject approach Pickup's close-range
|
||||
/// branch already arms on, giving the walk immediate visual feel. Unlike
|
||||
/// the original (send-immediately) design, the wire send is now GATED on
|
||||
/// natural arrival — live testing against the user's local ACE server
|
||||
/// showed retail's own "no range check, send immediately" assumption
|
||||
/// does not hold there: ACE's <c>Player.HandleActionUseItem</c> polls
|
||||
/// for the player to actually reach use range before calling
|
||||
/// <c>ActOnUse</c>, and a Use that arrives too early is silently lost
|
||||
/// (the vendor's cosmetic greeting fires — a distance-only reaction
|
||||
/// independent of Use — but <c>ApproachVendor</c> never comes). Nothing
|
||||
/// dispatches until <see cref="SelectionInteractionController.OnNaturalMoveToComplete"/>
|
||||
/// fires; a SECOND natural-completion call must not re-dispatch (the
|
||||
/// pending-use token is consumed on first resolve, exactly like Pickup's
|
||||
/// pending-pickup token).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FarUseApproachesThenDispatchesImmediatelyAndDoesNotRetryOnArrival()
|
||||
public void FarUseApproachesThenDispatchesOnNaturalArrival()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.SetApproach(closeRange: false);
|
||||
|
|
@ -475,6 +483,11 @@ public sealed class SelectionInteractionControllerTests
|
|||
h.Controller.SendUse(Target);
|
||||
|
||||
PlayerInteractionMovementSinkAssertSingleApproach(h, Target);
|
||||
// Armed, not yet sent — the whole point of the fix.
|
||||
Assert.Empty(h.Transport.Uses);
|
||||
|
||||
h.Controller.OnNaturalMoveToComplete();
|
||||
|
||||
Assert.Equal(new[] { Target }, h.Transport.Uses);
|
||||
|
||||
h.Controller.OnNaturalMoveToComplete();
|
||||
|
|
@ -483,10 +496,10 @@ public sealed class SelectionInteractionControllerTests
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// C1 cancellation coverage: a second far Use command (the player picked
|
||||
/// a new target, i.e. "moved on") supersedes the first local approach
|
||||
/// cleanly — no exception, no missing/duplicated dispatch, no leaked
|
||||
/// pending-pickup state (Use never arms one).
|
||||
/// C1/G3 cancellation coverage: a second far Use command (the player
|
||||
/// picked a new target, i.e. "moved on") supersedes the first local
|
||||
/// approach cleanly — the FIRST target's armed Use is cancelled (never
|
||||
/// sent), and only the SECOND dispatches, on ITS OWN natural arrival.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void NewFarUseCommandSupersedesThePreviousApproachCleanly()
|
||||
|
|
@ -509,23 +522,25 @@ public sealed class SelectionInteractionControllerTests
|
|||
Assert.Equal(2, h.Movement.Approaches.Count);
|
||||
Assert.Equal(Target, h.Movement.Approaches[0].Target.ServerGuid);
|
||||
Assert.Equal(otherTarget, h.Movement.Approaches[1].Target.ServerGuid);
|
||||
Assert.Equal(new[] { Target, otherTarget }, h.Transport.Uses);
|
||||
// Neither has sent yet — both are armed/superseded, not dispatched.
|
||||
Assert.Empty(h.Transport.Uses);
|
||||
|
||||
h.Controller.OnNaturalMoveToComplete();
|
||||
|
||||
Assert.Equal(new[] { Target, otherTarget }, h.Transport.Uses);
|
||||
// Only the surviving (second) approach's Use goes out.
|
||||
Assert.Equal(new[] { otherTarget }, h.Transport.Uses);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// C1 cancellation coverage: the underlying MoveTo controller cancelling
|
||||
/// out from under a far Use's local approach (player moved away with
|
||||
/// WASD, or any other source of <see cref="WeenieError"/>) must not
|
||||
/// retract or duplicate the Use, which already went out unconditionally
|
||||
/// at click time — Use holds no pending-pickup state for
|
||||
/// <c>OnMoveToCancelled</c> to touch.
|
||||
/// C1/G3 cancellation coverage: the underlying MoveTo controller
|
||||
/// cancelling out from under a far Use's local approach (player moved
|
||||
/// away with WASD, or any other source of <see cref="WeenieError"/>)
|
||||
/// must cancel the ARMED (not-yet-sent) Use — retail's own server-side
|
||||
/// poll would never have seen the player arrive either, so nothing
|
||||
/// should reach the wire.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MovingAwayDuringAFarUseApproachDoesNotAffectTheAlreadyDispatchedUse()
|
||||
public void MovingAwayDuringAFarUseApproachCancelsTheArmedUse()
|
||||
{
|
||||
var h = new Harness();
|
||||
h.SetApproach(closeRange: false);
|
||||
|
|
@ -534,7 +549,7 @@ public sealed class SelectionInteractionControllerTests
|
|||
h.Controller.OnMoveToCancelled(WeenieError.ActionCancelled);
|
||||
h.Controller.OnNaturalMoveToComplete();
|
||||
|
||||
Assert.Equal(new[] { Target }, h.Transport.Uses);
|
||||
Assert.Empty(h.Transport.Uses);
|
||||
}
|
||||
|
||||
private static void PlayerInteractionMovementSinkAssertSingleApproach(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue