feat(physics): R5-V4 behavioral slice — head stance dispatch (all mt) + #164 autonomy bit + mt-0 wire flags

Three unpack_movement parity items (facade deferred per the handoff's
own optional clause — see r5-wiring-handoff §V4 status):

1. HEAD style-on-change (0x00524440 @00524502-0052452c): both GameWindow
   routing heads (remote + player) now dispatch DoMotion(style, ctor
   defaults) when the UM's stance differs from the interp's current
   style, BEFORE the movement-type routing — for EVERY type. Previously
   style applied only through the mt-0 funnel copy, so a chase/turn UM
   (mt 6-9) carrying a stance change started the move in the OLD stance
   (a monster charged in NonCombat posture until the next mt-0). The
   RetailObserverTraceConformanceTests exclusion note updated — the
   trace filter stays (head calls can't appear in a
   MoveToInterpretedState replay) but the production gap it pointed at
   is closed.

2. #164 (closes): the action-replay loop threads each action's autonomy
   into the dispatch params (Autonomous = the 0x1000 splice, raw
   305982) — replayed actions enter the interpreted actions list with
   their real autonomy instead of ctor-default false.

3. mt-0 wire flags consumed (UpdateMotion parsed them since R4-V3):
   0x1 StickToObject → CPhysicsObj::stick_to_object port (0x005127e0:
   resolve target, PartArray radii — 0 when shapeless, guid-as-is for
   acdream's flat entity table — → PositionManager.StickTo; unresolvable
   target → no stick), at BOTH case-0 tails in retail order
   (@00524583-0052458e: funnel apply → stick → longjump flag);
   0x2 StandingLongJump → Motion.StandingLongJump, UNCONDITIONAL write
   (absent flag clears — retail @0052458e). ServerMotionState gains the
   StandingLongJump field.

Conformance: ChaseArm_WithStanceChange_AppliesStanceBeforeTheChase
(harness mirrors the routing-head dispatch) +
Actions_ReplayCarriesAutonomyIntoTheInterpretedList. Suite 4041 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-05 10:18:26 +02:00
parent 530453067a
commit f423884bd1
9 changed files with 213 additions and 20 deletions

View file

@ -2860,7 +2860,7 @@ public sealed class MotionInterpreter : IMotionDoneSink
if (IsLocalPlayer && a.Autonomous) continue;
ServerActionStamp = incoming;
DispatchInterpretedMotion(a.Command, a.Speed, sink);
DispatchInterpretedMotion(a.Command, a.Speed, a.Autonomous, sink);
}
}
@ -3291,10 +3291,16 @@ public sealed class MotionInterpreter : IMotionDoneSink
/// <c>ModifyInterpretedState = true</c>, so replayed actions DO enter the
/// interpreted actions list via <c>InterpretedMotionState::AddAction</c>),
/// unlike the apply pass's rewritten params (see
/// <see cref="ApplyInterpretedMovement"/>). Known residual gap: retail
/// also sets the params' <c>Autonomous</c> bit (0x1000) per action (raw
/// 305982) — not yet threaded (tracked in ISSUES, #161 follow-up note).
/// <see cref="ApplyInterpretedMovement"/>). R5-V4 (#164): the params'
/// <c>Autonomous</c> bit (0x1000) is spliced from the ACTION's autonomy
/// flag — retail raw 305982:
/// <c>var_28 ^= ((action.autonomous &lt;&lt; 0xc) ^ var_28) &amp; 0x1000</c>
/// — so a replayed action enters the state lists
/// (<c>AddAction(motion, speed, stamp, autonomous)</c>) with its real
/// autonomy instead of the ctor-default false.
/// </summary>
private WeenieError DispatchInterpretedMotion(uint motion, float speed, IInterpretedMotionSink? sink)
=> DoInterpretedMotion(motion, new MovementParameters { Speed = speed }, sink);
private WeenieError DispatchInterpretedMotion(
uint motion, float speed, bool autonomous, IInterpretedMotionSink? sink)
=> DoInterpretedMotion(
motion, new MovementParameters { Speed = speed, Autonomous = autonomous }, sink);
}