fix(items): preserve wand stance and corpse use order

Keep active-combat AutoWield intent through ACE's pre-wield transition and queued trailing peace notice, while allowing explicit combat input to supersede it.

Queue one-shot item interactions until the frame's movement edge has been serialized, so a movement release cannot cancel ACE's server-side corpse approach callback.

Release build succeeds and all 5,890 runnable tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 19:32:01 +02:00
parent 0392c6d721
commit e6dd8bf6fa
10 changed files with 303 additions and 22 deletions

View file

@ -165,6 +165,7 @@ public sealed class GameWindow : IDisposable
private readonly AcDream.App.Physics.RemotePhysicsUpdater _remotePhysicsUpdater;
private readonly AcDream.App.Input.LocalPlayerProjectionController _localPlayerProjection;
private readonly AcDream.App.Input.LocalPlayerOutboundController _localPlayerOutbound;
private readonly AcDream.App.Input.OutboundInteractionQueue _outboundInteractions = new();
private readonly AcDream.App.Input.RetailLocalPlayerFrameController _localPlayerFrame;
private readonly AcDream.App.World.RetailLiveFrameCoordinator _liveFrameCoordinator;
private AcDream.App.Physics.RemoteTeleportController? _remoteTeleportController;
@ -2917,6 +2918,7 @@ public sealed class GameWindow : IDisposable
_magicRuntime?.Reset();
_itemInteractionController?.ClearBusy();
_selection.Reset();
_outboundInteractions.Clear();
_pendingPostArrivalAction = null;
_particleVisibility.Reset();
try
@ -10775,6 +10777,12 @@ public sealed class GameWindow : IDisposable
// remote objects. Its outbound movement snapshot is completed here,
// before SmartBox dispatch can apply F751/ForcePosition.
_localPlayerFrame.AdvanceBeforeNetwork(dt);
// Retail input ordering: movement edges reach MoveToState before a
// later Use/PickUp action from the same input frame. ACE cancels an
// active server MoveTo chain when it receives MoveToState, so sending
// Use directly from Silk's key callback inverted that order and left
// the client walking to a corpse whose server callback was cancelled.
_outboundInteractions.Drain();
_scriptRunner?.PublishTime(_physicsScriptGameTime);
if (_liveEntities is { } liveEntities)
{
@ -12966,7 +12974,7 @@ public sealed class GameWindow : IDisposable
case AcDream.UI.Abstractions.Input.InputAction.SelectionPickUp:
if (_selection.SelectedObjectId is uint pickupTarget)
SendPickUp(pickupTarget);
_outboundInteractions.Enqueue(() => SendPickUp(pickupTarget));
else
_debugVm?.AddToast("Nothing selected");
break;
@ -13004,6 +13012,7 @@ public sealed class GameWindow : IDisposable
var nextMode = AcDream.Core.Combat.CombatInputPlanner.ToggleMode(
Combat.CurrentMode,
defaultMode);
_itemInteractionController?.NotifyExplicitCombatModeRequest();
_liveSession.SendChangeCombatMode(nextMode);
Combat.SetCombatMode(nextMode);
string text = $"Combat mode {nextMode}";
@ -13237,7 +13246,15 @@ public sealed class GameWindow : IDisposable
Console.WriteLine(System.FormattableString.Invariant(
$"[B.7] pick-info guid=0x{guid:X8} itemType=0x{rawItemType:X8} pwd=0x{pwdBits:X8} use={useStr} useRadius={radStr} scale={pickScale:F2} setup={setupStr} color=({col.R},{col.G},{col.B})"));
_debugVm?.AddToast($"Selected: {label}");
if (useImmediately) SendUse(guid);
if (useImmediately)
{
// Route world double-click through the same retail
// ItemHolder policy as keyboard Use. This both establishes
// ClientUISystem::groundObject for containers and preserves
// movement-release-before-Use wire ordering.
_outboundInteractions.Enqueue(() =>
_itemInteractionController?.ActivateItem(guid));
}
}
else
{
@ -13259,7 +13276,9 @@ public sealed class GameWindow : IDisposable
// the same policy owner used by the toolbar. Keeping a second classifier here
// made BF_CORPSE containers look pickupable and sent PutItemInContainer,
// which ACE correctly rejected as Stuck (0x0029).
_itemInteractionController?.UseSelectedOrEnterMode(sel);
uint selected = sel;
_outboundInteractions.Enqueue(() =>
_itemInteractionController?.UseSelectedOrEnterMode(selected));
}
private void SendUse(uint guid)