fix(#170): armed moveto always ticks UseTime — the SERVERVEL branch starved the chase

The "sustain the run" residual. The handoff's "Ready stop-node backlog
drains a beat slower than retail" framing was DISPROVEN: a new full-stack
offline harness (RemoteChaseEndToEndHarnessTests — real MoveToManager +
MotionInterpreter + AnimationSequencer + MotionTableDispatchSink + the
manual omega integration, wired field-for-field like
EnsureRemoteMotionBindings and ticked in TickAnimations' exact phase
order) proves the Core turn/run/drain pipeline healthy: the chase turn
completes in <1 s both directions, BeginMoveForward installs per arm, the
run sustains across re-arms and attack swings, and pending_motions fully
empties (retail cdb invariant add_to_queue == MotionDone).

The real mechanism (launch-drainq.log, corrected per-guid attribution —
the previous session's timeline mis-attributed [mvto] lines that fire in
the network phase): funnel per chasing scamp was 16 mt-6 arms -> 11
dispatched turns -> ONE BeginMoveForward. Any NPC receiving
UpdatePositions gets HasServerVelocity=true (synthesized from position
deltas even when the wire carries no velocity), and the grounded per-tick
branch routed those to the SERVERVEL leg, which SKIPS
MoveToManager.UseTime — [npc-tick] literally logged
"branch=SERVERVEL (skips UseTime) mtState=MoveToObject". The armed
moveto was starved for exactly the duration of the server-side chase:
legs stayed in Ready while the body glided on synthesized velocity (the
#170 slide); the manager only woke in UP-silent gaps (creature stopped
server-side) and its stale-heading turn was interrupted by the next UM
before reaching BeginMoveForward.

Retail runs MovementManager::UseTime UNCONDITIONALLY every tick
(CPhysicsObj::UpdateObjectInternal 0x005156b0, call @0x00515998) and has
no wire-velocity leg-driver anywhere; between UPs a moveto-driven body
translates from the motion state (get_state_velocity) with UP hard-snaps
correcting drift. Fix: an armed moveto (MovementTypeState != Invalid)
always takes the MOVETO leg; SERVERVEL remains only as the legacy
fallback for entities without a moveto (scripted paths / missiles).

Register: TS-41 (the narrowed SERVERVEL stopgap), TS-42 (drain-order
divergence also pinned this session: acdream drains AnimDone->MotionDone
AFTER HandleTargetting/MoveTo.UseTime; retail's process_hooks
@0x00512d3d runs BEFORE TargetManager/MovementManager in
UpdateObjectInternal — one frame of extra latency, R6 scope).

New conformance: RemoteChaseEndToEndHarnessTests (3 scenarios + theory)
+ RemoteChaseDrainBisectTests (the drain-chain pin; its first run also
demonstrated the TS-40 InWorld=false link-strip wedge shape — harness
bodies must replicate the live RemoteMotion construction).

ISSUES #170 updated (awaiting user visual gate; probes stay until then);
handoff doc superseded-note added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-04 21:59:59 +02:00
parent e744192361
commit 1051fc83c6
6 changed files with 811 additions and 20 deletions

View file

@ -48,25 +48,33 @@ Copy this block when adding a new issue:
## #170 — Remote creature chase+attack renders wrong vs retail (glide, over-frequency, uniform attack anims)
**Status:** OPEN — **PARTIAL FIX landed `427332ac` (2026-07-04); residual = "sustain the run".**
Root-caused end-to-end with live retail cdb tracing + acdream probes. The chase run
cycle is manufactured client-side from mt-6 (`HandleUpdateTarget → MoveToObject_Internal
→ TurnToHeading node completes via UseTime → BeginMoveForward → RunForward substate`);
`BeginTurnToHeading` bails while `CMotionInterp.motions_pending` is non-empty
(retail-faithful). acdream's `pending_motions` EXPLODED to ~1.3M entries because the
NPC per-tick called `apply_current_movement` EVERY FRAME (re-dispatching stance+attack+
stops), so `MotionsPending()` stayed permanently true → the chase turn never started →
no RunForward → slide. FIX = delete the per-frame `apply_current_movement` (GameWindow
~9992; retail dispatches per UM, `add_to_queue==MotionDone`). VERIFIED: flood 1.3M→~1,
"stuck attack" GONE (user-confirmed), run installs (BeginMoveForward 1→10). **RESIDUAL
(next session):** still blocked motionsPending=True 94% by a `Ready` (0x41000003)
stop-node backlog that drains a beat slower than retail (~10 run-starts vs retail 21 →
twitches+glides). Fix the R2/R3 `Ready`-stop DRAIN so acdream fully empties like retail
(retail is the oracle — cdb the drain). **SSOT + pickup:**
**Status:** OPEN — **residual fix landed 2026-07-04 ("sustain the run" — SERVERVEL
starvation); AWAITING VISUAL GATE.** Fix #1 (`427332ac`): the per-frame
`apply_current_movement` re-dispatch flooded `pending_motions` to ~1.3M → deleted;
flood 1.3M→~1, "stuck attack" gone (user-confirmed), run installs 1→10. Fix #2 (this
session): **the "slow Ready drain" framing was WRONG** — a full-stack offline harness
(`RemoteChaseEndToEndHarnessTests`, real MoveToManager + MotionInterpreter +
AnimationSequencer + omega integration in GameWindow's exact tick order) proved the
Core drain/turn/run pipeline healthy (turn completes <1 s, run sustains, add==done).
Corrected per-guid log attribution (launch-drainq.log) showed the REAL funnel: 16 arms
→ 11 turns dispatched → **1 run install**, because the per-tick branch arbitration
routed any UP-receiving NPC to the SERVERVEL leg (`HasServerVelocity` synthesized from
position deltas) which **skips `MoveToManager.UseTime`** — the armed moveto was starved
for exactly the duration of the server-side chase (`[npc-tick] branch=SERVERVEL (skips
UseTime) mtState=MoveToObject`), legs stayed Ready while the body glided on synthesized
velocity; the manager only woke in UP-silent gaps and was interrupted by the next UM.
Retail runs `MovementManager::UseTime` UNCONDITIONALLY per tick
(`CPhysicsObj::UpdateObjectInternal` 0x005156b0 @0x00515998) and has no wire-velocity
leg-driver. FIX: an armed moveto (`MovementTypeState != Invalid`) always takes the
MOVETO leg; SERVERVEL remains only for non-moveto entities (register TS-41; drain-order
one-frame divergence also pinned + filed as TS-42). **SSOT + pickup:**
`docs/research/2026-07-04-170-creature-run-handoff.md` +
`docs/research/2026-07-04-170-pickup-prompt.md`. SUPERSEDES the earlier
MovementManager-coexistence hypothesis (`eb423fb7`, wrong) and the velocity fix
(`d2ccc80e`, correct but position-only). #159 was a red herring here.
`docs/research/2026-07-04-170-pickup-prompt.md` (residual sections superseded by this
entry). SUPERSEDES the earlier MovementManager-coexistence hypothesis (`eb423fb7`,
wrong shape — but the starvation IS a coexistence bug at the tick-arbitration altitude)
and keeps the `d2ccc80e` velocity fix. #159 was a red herring here. NEXT: user visual
gate (retail side-by-side, chase a fleeing player) → then strip the #170 probes
(`s_mvtoDiag`, `s_drainDiag`, `[npc-tick]`, `UM ↳ actions`) and close.
**Severity:** MEDIUM (visual — remote combat / aggro)
**Filed:** 2026-07-03 (user retail side-by-side during the R5-V2 visual gate)
**Component:** animation, remote entity, combat