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
|
|
@ -153,12 +153,17 @@ public sealed class MoveToManager
|
|||
/// <summary>
|
||||
/// CLIENT ADDITION — NOT retail (decomp §7e / do-not-invent list):
|
||||
/// <c>CleanUpAndCallWeenie</c> contains no weenie call in this build
|
||||
/// (the name is vestigial; body ≡ CleanUp + StopCompletely). This seam
|
||||
/// stands in for ACE's server-side <c>OnMoveComplete</c> notification so
|
||||
/// the App layer can re-anchor AD-27 (Use/PickUp re-send on arrival).
|
||||
/// Fires from <see cref="CleanUpAndCallWeenie"/> ONLY (never from plain
|
||||
/// <see cref="CleanUp"/>/<see cref="CancelMoveTo"/>). Optional — null is
|
||||
/// a silent no-op.
|
||||
/// (the name is vestigial; body ≡ CleanUp + StopCompletely), and retail
|
||||
/// notifies NOTHING on arrival (§4b's empty-queue completion is inline
|
||||
/// CleanUp + StopCompletely). This seam stands in for ACE's server-side
|
||||
/// <c>OnMoveComplete</c> notification so the App layer can re-anchor
|
||||
/// AD-27 (Use/PickUp re-send on arrival). Fires with
|
||||
/// <see cref="WeenieError.None"/> on NATURAL COMPLETION — the
|
||||
/// <see cref="BeginNextNode"/> empty-queue exits (both sticky and
|
||||
/// non-sticky) and <see cref="CleanUpAndCallWeenie"/>'s instant-success
|
||||
/// path — and NEVER from <see cref="CancelMoveTo"/>/plain
|
||||
/// <see cref="CleanUp"/> (a cancel is not an arrival; AD-27's re-send
|
||||
/// must not fire on user interrupt). Optional — null is a silent no-op.
|
||||
/// </summary>
|
||||
public Action<WeenieError>? MoveToComplete { get; set; }
|
||||
|
||||
|
|
@ -701,11 +706,17 @@ public sealed class MoveToManager
|
|||
if (HasPhysicsObj) _stopCompletely();
|
||||
|
||||
StickTo?.Invoke(tlid, radius, height);
|
||||
// CLIENT ADDITION (see MoveToComplete doc): natural completion.
|
||||
// Reentrancy-safe: CleanUp reset movement_type to Invalid BEFORE
|
||||
// the stop, so the stop's interrupt→CancelMoveTo chain no-oped.
|
||||
MoveToComplete?.Invoke(WeenieError.None);
|
||||
return;
|
||||
}
|
||||
|
||||
CleanUp();
|
||||
if (HasPhysicsObj) _stopCompletely();
|
||||
// CLIENT ADDITION (see MoveToComplete doc): natural completion.
|
||||
MoveToComplete?.Invoke(WeenieError.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1497,9 +1508,10 @@ public sealed class MoveToManager
|
|||
/// raw 306740-306752). Despite the name, contains NO weenie call in this
|
||||
/// build (decomp §7e — the compiled-out server-side callback; body ≡
|
||||
/// <see cref="CleanUp"/> + StopCompletely). <see cref="MoveToComplete"/>
|
||||
/// is a documented CLIENT ADDITION firing HERE ONLY (see its doc)
|
||||
/// standing in for ACE's server-side <c>OnMoveComplete</c> — do NOT
|
||||
/// present it as retail behavior.
|
||||
/// is a documented CLIENT ADDITION (see its doc) firing here and at
|
||||
/// <see cref="BeginNextNode"/>'s empty-queue completion, standing in for
|
||||
/// ACE's server-side <c>OnMoveComplete</c> — do NOT present it as retail
|
||||
/// behavior.
|
||||
/// </summary>
|
||||
public void CleanUpAndCallWeenie(WeenieError error)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -165,6 +165,37 @@ public static class MoveToMath
|
|||
return Quaternion.CreateFromAxisAngle(Vector3.UnitZ, yaw - MathF.PI / 2f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R4-V5: the scalar leg of <see cref="GetHeading"/> for bodies whose
|
||||
/// authoritative facing is a yaw ANGLE rather than a quaternion (the
|
||||
/// local player: <c>PlayerMovementController.Yaw</c>, radians, Yaw=0
|
||||
/// faces +X, re-synced into the body quaternion every Update). Same P5
|
||||
/// bridge: <c>heading = (90 - yawDeg) mod 360</c>.
|
||||
/// </summary>
|
||||
public static float HeadingFromYaw(float yawRad)
|
||||
{
|
||||
float headingDeg = 90f - yawRad * (180f / MathF.PI);
|
||||
headingDeg %= 360f;
|
||||
if (headingDeg < 0f) headingDeg += 360f;
|
||||
return headingDeg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R4-V5: exact inverse of <see cref="HeadingFromYaw"/> — the
|
||||
/// <c>set_heading</c> seam for yaw-authoritative bodies (the local
|
||||
/// player's heading snap must write <c>Yaw</c>, NOT the body
|
||||
/// quaternion, which the controller re-derives from Yaw every frame).
|
||||
/// Returns radians wrapped to [-π, π] matching the controller's own
|
||||
/// wrap discipline.
|
||||
/// </summary>
|
||||
public static float YawFromHeading(float headingDeg)
|
||||
{
|
||||
float yaw = (90f - headingDeg) * (MathF.PI / 180f);
|
||||
while (yaw > MathF.PI) yaw -= 2f * MathF.PI;
|
||||
while (yaw < -MathF.PI) yaw += 2f * MathF.PI;
|
||||
return yaw;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>Position::cylinder_distance</c>, the pure-math shape
|
||||
/// consumed by <c>MoveToManager::GetCurrentDistance</c>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue