fix #270: fire ReportExhaustion on the stamina-exhaustion edge only, not every stats tick

Retail calls CPhysicsObj::report_exhaustion from exactly one site -
CommandInterpreter::HandleExhaustion (0x006b3c70), a notification handler
for the stamina-exhaustion EVENT. Campaign P P1 wired it to every
movement-stats application instead (every stamina regen/drain tick), and
each call re-dispatches the current movement state through the animation
sink - truncating any in-flight action animation. The diagnostic session
log shows 490 spurious casting-stance re-queues in one short session:
'sometimes stuck in spell animations' was every stamina tick that
collided with a cast gesture's play window.

The re-apply now fires only when the exhausted state (stamina == 0)
transitions, matching retail's event semantics. Stats still reach
PlayerWeenie immediately via RuntimeMovementSkillProjection.ApplyTo.

Also adds the [remote-edge] probe (rides ACDREAM_DUMP_MOTION=1): one
line per remote HitGround/LeaveGround - each such edge drains the
mover's pending action animations (retail HandleEnterWorld), the
working theory for intermittently missing monster attack swings.

Complete Release suite: 10,026 passed / 5 skips / 0 failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-30 22:11:20 +02:00
parent 5788fdaa02
commit a46c8e65b2
4 changed files with 101 additions and 10 deletions

View file

@ -747,6 +747,26 @@ internal sealed class RuntimeRemotePhysicsUpdater
rm.Body.Position = resolved.Position;
if (resolved.CellId != 0)
committedCellId = resolved.CellId;
// [remote-edge] probe (stuck-cast/missing-attack investigation,
// 2026-07-30): each ground edge drains the mover's pending
// action animations (retail HandleEnterWorld) — one line per
// edge correlates eaten attack gestures with contact flickers.
Action hitGround = rm.Movement.HitGround;
Action leaveGround = rm.Motion.LeaveGround;
if (AcDream.Core.Physics.PhysicsDiagnostics.DumpMotionEnabled)
{
uint edgeGuid = record.ServerGuid;
hitGround = () =>
{
Console.WriteLine($"[remote-edge] guid={edgeGuid:X8} HitGround");
rm.Movement.HitGround();
};
leaveGround = () =>
{
Console.WriteLine($"[remote-edge] guid={edgeGuid:X8} LeaveGround");
rm.Motion.LeaveGround();
};
}
if (!AcDream.Core.Physics.PhysicsObjUpdate.CommitSetPositionTransition(
rm.Body,
resolved.InContact,
@ -755,8 +775,8 @@ internal sealed class RuntimeRemotePhysicsUpdater
resolved.CollisionNormal,
previousContact,
previousOnWalkable,
rm.Movement.HitGround,
rm.Motion.LeaveGround,
hitGround,
leaveGround,
() => IsCurrentOwner(
record,
rm,