feat(R4-V5): LOCAL PLAYER cutover - B.6 auto-walk DELETED; P1 autonomous gate ported; TS-36 bound (closes M1-local, M9, M10, M17; retires TS-36, AD-26; re-anchors AD-27, AP-23)
The local player now runs server MoveTos through the same verbatim MoveToManager remotes got in V4. One commit, GameWindow + controller, per the no-fan-out rule for coupled slices. P1 gate (V0-pins.md P1, ported verbatim): CPhysics::SetObjectMovement (0x00509690 @0050972e) drops any movement event whose wire autonomous byte is set when the addressed object IsThePlayer - ACE's self-addressed MoveToState reflection (MovementData.cs:162 IsAutonomous=1) never reaches unpack_movement, which is what makes the retail unconditional unpack-head interrupt safe. Gate placed AFTER the sequence gates (retail order). The stale "ACE follows every mt=0x06 with an mt=0x00" comment block dies with the code it excused (its causal story was pre-#75; refuted in V0-pins P1). Run-rate re-anchor (P1 contingency NOT needed - no AD row): the echo tap ApplyServerRunRate is deleted outright. Both retail feeds already exist: PlayerDescription skills via SetCharacterSkills (K-fix7) into InqRunRate (preferred by apply_run_to_command/get_state_velocity), and the mt-6/7 my_run_rate wire write (M13) now performed for the player by the shared RouteServerMoveTo. The tap's InterpretedState.ForwardSpeed overwrite was a pre-R3 mechanism that fought the ported machinery. B.6 auto-walk deleted wholesale (~330 lines): fields, Begin/End/ DriveServerAutoWalk, IsServerAutoWalking, AutoWalkArrived, the autoWalkConsumedMotion gates, the #69 turn-dir edge synthesizer, and the relocated AutoWalkArrivalEpsilon/AutoWalkTurnRateFor constants (AD-26 retired - the invented 5/30-degree bands are gone; arrival is retail's distance predicate; turn-first is the TurnToHeading node). TS-36 retired: Motion.InterruptCurrentMovement binds to MoveTo.CancelMoveTo(ActionCancelled). Movement-key edges (ctor-default params carry the 0x8000 CancelMoveTo bit), Shift (set_hold_run interrupt:true), jump(), StopCompletely, and teleport all cancel a running moveto through the retail chain - verified by controller-level tests, not assumed. MoveToComplete seam WIDENED to natural completion (Core): retail's BeginNextNode empty-queue completion is inline CleanUp+StopCompletely (raw @00529d47) and notifies nothing - the client-addition seam had to fire there (both sticky and non-sticky exits) or AD-27's deferred close-range Use/PickUp re-send never fires. Never fires on CancelMoveTo. AD-27 re-anchored from the deleted AutoWalkArrived event. InstallSpeculativeTurnToTarget rewired through the player's manager (retail 9a/9b client-initiated shape): close-range -> TurnToObject, far -> MoveToObject; AP-23 radius buckets + the #77 CanCharge prediction survive as the params source (row re-anchored). Per-tick: MoveTo.UseTime() at the old DriveServerAutoWalk slot (provisional until R6, per the plan's placement decision); the P4 player-side TargetTracker twin (fields + pre-Update feed) mirrors the remote adapter (AP-79 row widened). HitGround dual-call added on the player landing edge AND the remote landing site - the latter closes a V4 wiring-contract gap the adversarial review caught (retail 2d order: minterp then moveto; without it a landing NPC's moveto never re-arms). Also from the adversarial review: the mt-8 unresolvable-target degrade now performs retail's params.desired_heading = wire_heading substitution (decomp 2f case 8; invisible against ACE per P6, required for the verbatim degrade); the V4 remote MoveToManager binding gets a real curTime clock (the ctor stub advanced 1/30s per READ, skewing the progress/fail-distance windows - note: the pending V4 NPC smoke ran on the skewed clock); TS-33 row extended with the orientation-diff gap (ApproxPositionEqual vs retail Frame::is_equal full-frame compare - a stationary heading snap does not reach the wire; masked against ACE, R7 outbound scope owns the fix). MoveToMath gains HeadingFromYaw/YawFromHeading (the P5 scalar bridge for yaw-authoritative bodies - the player's heading snap must write Yaw, not the quaternion the controller re-derives every frame). Tests: 3,956 green (+8). New: MoveToManagerCompletionSeamTests (arrival fires once with None, no refire, cancel never fires, sticky handoff order) + PlayerMoveToCutoverTests (EnterPlayerModeNow-shape rig: walks to arrival with zero user input and zero MotionStateChanged frames - the #75 invariant by construction; TurnToHeading rotates Yaw and snaps exact; W-edge and jump cancel without firing complete). W6 edge suite retargeted from the deleted echo tap to a direct apply pass (the regression lives in ApplyInterpretedMovement, not the wire trigger). Spec: docs/research/2026-07-03-r4-moveto/r4-port-plan.md section 3 V5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e4553a0262
commit
b3decdfac6
9 changed files with 947 additions and 688 deletions
|
|
@ -107,6 +107,21 @@ public class W6EdgeDrivenMovementTests
|
|||
return controller;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The full apply pass the W6b live bug rode in on. Pre-R4-V5 the
|
||||
/// trigger was <c>ApplyServerRunRate</c> (the ACE autonomous-echo tap);
|
||||
/// V5's P1 gate drops that echo before it reaches the player, and the
|
||||
/// tap is deleted — but the regression these tests pin lives in
|
||||
/// <c>ApplyInterpretedMovement</c>'s entry-caching, which any
|
||||
/// apply_current_movement pass (HitGround re-apply, future R6 per-tick
|
||||
/// order) still exercises. Same two statements the deleted tap ran.
|
||||
/// </summary>
|
||||
private static void RunApplyPass(PlayerMovementController controller, float forwardSpeed)
|
||||
{
|
||||
controller.Motion.InterpretedState.ForwardSpeed = forwardSpeed;
|
||||
controller.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyPass_WithRealSink_ForwardSelfHeals()
|
||||
{
|
||||
|
|
@ -120,8 +135,9 @@ public class W6EdgeDrivenMovementTests
|
|||
|
||||
Assert.Equal(Run, controller.Motion.InterpretedState.ForwardCommand);
|
||||
|
||||
// The live killer: the UM-echo pass (~10Hz in the real client).
|
||||
controller.ApplyServerRunRate(4.5f);
|
||||
// The live killer: a full apply pass mid-hold (was the ~10Hz
|
||||
// UM-echo tap pre-V5; see RunApplyPass).
|
||||
RunApplyPass(controller, 4.5f);
|
||||
|
||||
Assert.Equal(Run, controller.Motion.InterpretedState.ForwardCommand);
|
||||
Assert.True(controller.Motion.get_state_velocity().Length() > 1f,
|
||||
|
|
@ -139,7 +155,7 @@ public class W6EdgeDrivenMovementTests
|
|||
for (int f = 0; f < 120; f++)
|
||||
{
|
||||
if (f % 6 == 3)
|
||||
controller.ApplyServerRunRate(4.5f); // ACE echo cadence
|
||||
RunApplyPass(controller, 4.5f); // ex-ACE-echo cadence
|
||||
pos = controller.Update(1f / 60f, input).Position;
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +179,7 @@ public class W6EdgeDrivenMovementTests
|
|||
// Shift pressed (walk) — the set_hold_run edge demotes to walk.
|
||||
for (int f = 0; f < 30; f++)
|
||||
{
|
||||
if (f == 10) controller.ApplyServerRunRate(1.0f); // echo mid-walk
|
||||
if (f == 10) RunApplyPass(controller, 1.0f); // apply pass mid-walk
|
||||
controller.Update(1f / 60f, new MovementInput { Forward = true, Run = false });
|
||||
}
|
||||
Assert.Equal(Walk, controller.Motion.InterpretedState.ForwardCommand);
|
||||
|
|
@ -171,7 +187,7 @@ public class W6EdgeDrivenMovementTests
|
|||
// Shift released — promote back to run; survives another echo.
|
||||
for (int f = 0; f < 30; f++)
|
||||
{
|
||||
if (f == 10) controller.ApplyServerRunRate(4.5f);
|
||||
if (f == 10) RunApplyPass(controller, 4.5f);
|
||||
controller.Update(1f / 60f, new MovementInput { Forward = true, Run = true });
|
||||
}
|
||||
Assert.Equal(Run, controller.Motion.InterpretedState.ForwardCommand);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue