fix(#170): refresh remote body velocity from get_state_velocity (kill the attack glide)
Root cause (confirmed by a live ACDREAM_DUMP_MOTION capture of Mite Scamp
0x80000244 + the retail decomp): a chasing+attacking creature's attacks arrive
as the ForwardCommand of frequent mt-0 InterpretedMotionState UMs (66 attack UMs
0x62/63/64 vs 2 mt-6 chase MoveTos in the capture). Retail's get_state_velocity
(0x00527d50) computes the body's translation velocity from the current forward
command: WalkForward→3.12×spd, RunForward→4.0×spd, and 0 for everything else
(an attack) — so the creature plants its feet. acdream ALREADY has a faithful
get_state_velocity (returns 0 for a non-locomotion command; cross-checked vs
holtburger grounded_local_velocity's `_ => zero`), but it was never WIRED to the
remote body for entities with an animation sink: apply_current_movement's sink
path (all remotes have a DefaultSink) dispatches the animation and early-returns
BEFORE the set_local_velocity(get_state_velocity()) write, which lives only in
the no-sink fallback (MotionInterpreter ~1702). So a remote NPC's body.Velocity
was never recomputed from its motion state and kept the STALE run velocity from
the last chase — the body dead-reckoned forward at ~4 m/s while playing an
idle+attack pose ("glides after me").
Fix: after apply_current_movement in the grounded remote-NPC dead-reckon path
(GameWindow ~9992, restricted to remotes by serverGuid != player and to grounded
by OnWalkable), refresh rm.Body via set_local_velocity(get_state_velocity()) —
the exact write retail's apply_current_movement performs, reusing the verbatim
ported get_state_velocity. An attack forward command now resolves to 0, so the
creature stops and swings in place; RunForward still yields the run velocity.
Narrowest safe seam: the local player (which also has a sink) is excluded by the
loop's player guard, so its PlayerMovementController velocity path is untouched.
The earlier suspicion (#159 combat-command numbering) was a red herring — the
scamp's 0x62/63/64 were always in the correct block and the planner is unwired;
the wire even carries full attack variety, so "uniform" was the visual artifact
of rapid attacks over a gliding idle base, not a classification bug.
Tests: MotionVelocityPipelineTests.AttackForwardCommand_ZeroVelocity (4 cases)
pins get_state_velocity → 0 for the attack forward commands the fix depends on.
Core suite 2503 green. Visual gate pending (retail side-by-side: creature should
plant + step, not glide).
Ref: docs/research/named-retail get_state_velocity 0x00527d50; ISSUES #170.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fe24289ac5
commit
d2ccc80e59
2 changed files with 59 additions and 0 deletions
|
|
@ -9990,6 +9990,35 @@ public sealed class GameWindow : IDisposable
|
||||||
// per-tick shape ordinary locomotion uses.
|
// per-tick shape ordinary locomotion uses.
|
||||||
TickRemoteMoveTo(rm);
|
TickRemoteMoveTo(rm);
|
||||||
rm.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);
|
rm.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);
|
||||||
|
|
||||||
|
// #170 glide fix (2026-07-04): refresh the body's
|
||||||
|
// translation velocity from the current interpreted
|
||||||
|
// state, matching retail. apply_current_movement's
|
||||||
|
// SINK path (all remotes have a DefaultSink) only
|
||||||
|
// dispatches the ANIMATION — the
|
||||||
|
// get_state_velocity → set_local_velocity write
|
||||||
|
// that retail's apply_current_movement performs
|
||||||
|
// lives ONLY in acdream's no-sink fallback
|
||||||
|
// (MotionInterpreter.ApplyCurrentMovementInterpreted,
|
||||||
|
// ~1702), so a remote's body.Velocity is never
|
||||||
|
// recomputed and goes STALE. get_state_velocity
|
||||||
|
// (0x00527d50, already a verbatim port) returns
|
||||||
|
// WalkForward→3.12×spd, RunForward→4.0×spd, and 0
|
||||||
|
// for every other forward command — so when a
|
||||||
|
// chasing creature's ForwardCommand switches to an
|
||||||
|
// attack (0x1000006x, action-class), the body
|
||||||
|
// stops instead of coasting at the last run
|
||||||
|
// velocity while playing an idle+attack pose (the
|
||||||
|
// user-reported "glides after me"). Grounded only
|
||||||
|
// (this branch already asserted Contact+OnWalkable);
|
||||||
|
// airborne velocity is owned by the jump arc.
|
||||||
|
// Confirmed vs a live ACDREAM_DUMP_MOTION capture of
|
||||||
|
// Mite Scamp 0x80000244 (66 mt-0 attack UMs carrying
|
||||||
|
// ForwardCommand=0x62/63/64 vs 2 mt-6 chase UMs).
|
||||||
|
if (rm.Body.OnWalkable)
|
||||||
|
rm.Body.set_local_velocity(
|
||||||
|
rm.Motion.get_state_velocity(),
|
||||||
|
rm.Body.LastMoveWasAutonomous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -184,4 +184,34 @@ public sealed class MotionVelocityPipelineTests
|
||||||
Assert.Equal(0f, v.X, 5);
|
Assert.Equal(0f, v.X, 5);
|
||||||
Assert.Equal(0f, v.Y, 5);
|
Assert.Equal(0f, v.Y, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── attack / non-locomotion forward command (#170) ───────────────────────
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(0x10000062u)] // AttackHigh1
|
||||||
|
[InlineData(0x10000063u)] // AttackMed1
|
||||||
|
[InlineData(0x10000064u)] // AttackLow1
|
||||||
|
[InlineData(0x10000186u)] // AttackHigh4 (shifted late block)
|
||||||
|
public void AttackForwardCommand_ZeroVelocity(uint attackCommand)
|
||||||
|
{
|
||||||
|
// #170: an ATTACK forward command (action-class 0x1000006x/0x100001xx)
|
||||||
|
// is neither WalkForward (0x45000005) nor RunForward (0x44000007), so
|
||||||
|
// retail's get_state_velocity (0x00527d50) hits its `else → 0` branch —
|
||||||
|
// the creature plants its feet instead of coasting at the last run
|
||||||
|
// velocity. Cross-checked against holtburger grounded_local_velocity
|
||||||
|
// (`_ => Vector3::zero()`) and the retail decomp. This is the invariant
|
||||||
|
// the #170 glide fix relies on: GameWindow's remote dead-reckon now
|
||||||
|
// refreshes the body velocity from get_state_velocity each tick, so a
|
||||||
|
// creature that switches from a run-chase (ForwardCommand=RunForward)
|
||||||
|
// to an attack (ForwardCommand=0x1000006x) resolves to zero velocity
|
||||||
|
// and stops gliding.
|
||||||
|
var interp = MakeInterp(RunRate);
|
||||||
|
interp.InterpretedState.ForwardCommand = attackCommand;
|
||||||
|
interp.InterpretedState.ForwardSpeed = 0.97f;
|
||||||
|
|
||||||
|
var v = interp.get_state_velocity();
|
||||||
|
Assert.Equal(0f, v.X, 5);
|
||||||
|
Assert.Equal(0f, v.Y, 5);
|
||||||
|
Assert.Equal(0f, v.Z, 5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue