feat(retail): Phase B.6 — server-driven auto-walk done right
Closes #63, #69, #74, #75. Replaces the chain of Commit-B workarounds that compensated for ACE's MoveToChain getting cancelled by a leaked user-MoveToState packet during inbound auto-walk. The fix is architectural — auto-walk drives the body directly from the server-supplied path data, no player-input synthesis, no spurious wire-packet transitions, no grace-period band-aid. Architectural change (closes #75): PlayerMovementController.ApplyAutoWalkOverlay → DriveServerAutoWalk. - Steps Yaw toward target at retail-faithful turn rates. - Computes desired forward velocity from path runRate. - Calls _motion.DoMotion(WalkForward, speed) directly for the motion-interpreter state (drives animation cycle). - Sets _body.set_local_velocity directly when grounded. - Returns true to gate the user-input motion + velocity section in Update so user-input flow doesn't overwrite auto-walk velocity or motion state. Mirrors retail's MovementManager::PerformMovement case 6 (decomp 0x00524440) which never touches the user-input pipeline during server-controlled auto-walk. Wire-layer guard at GameWindow.cs:6419 retained as a SEMANTIC statement (`if (result.MotionStateChanged && !IsServerAutoWalking)`): user-MoveToState packets are for user-driven motion intent. During server-controlled auto-walk, the motion-state transitions caused by the animation override (RunForward / WalkForward / TurnLeft / TurnRight cycles) must not leak as user-cancellation packets. This is NOT the deleted 500ms grace-period band-aid; it's the wire-layer expressing the user-vs-server motion split. Animation plumbed for auto-walk phases (closes #69): - Moving forward → WalkForward (speed=1.0) / RunForward (speed=runRate) - Turn-first phase → TurnLeft / TurnRight (sign of yawStep) - Aligned-but-pre-step / arrival → no override (idle) Driven via _autoWalkMovingForwardThisFrame + _autoWalkTurnDirectionThisFrame fields set in DriveServerAutoWalk and read in the MovementResult construction at the bottom of Update. UpdatePlayerAnimation picks up the localAnimCmd as the highest-priority animation source. Walk/run threshold = 1.0m, retail-observed. ACE's wire-default of 15.0f is too generous; ACE's own physics layer uses 1.0f at MovementParameters.cs:50 (with the 15.0f line commented out) and Creature.cs:312 notes "default 15 distance seems too far". The formula matches retail's MovementParameters::get_command at decomp 0x0052aa00: running = (initialDist - distance_to_object) >= threshold, evaluated ONCE at chain start and held for the rest of the auto-walk (matches retail "runs all the way / walks all the way" behaviour). Wire-supplied threshold is ignored. Pickup gate (IsPickupableTarget) now uses BF_STUCK (acclient.h:6435, bit 0x4) to discriminate immovable scenery from real pickup items that share a Misc ItemType. Sign (pwd=0x14 with BF_STUCK) → blocked; spell component (pwd=0x10, no BF_STUCK) → allowed. ACE's PutItemInContainer (Player_Inventory.cs:831-836) responds with WeenieError.Stuck (0x29) on stuck items so the gate prevents wasted wire packets + a UX dead-end. R-key dispatch by target type. UseCurrentSelection's top-level IsUseableTarget gate was wrong (blocked USEABLE_NO=1 items that ARE pickupable). Reordered: 1. Creature → SendUse 2. Pickupable → SendPickUp 3. Useable → SendUse 4. Otherwise → "cannot be used" toast Each handler keeps its own gate. Matches retail's per-action server-side validation. AP cadence revert (closes #74). With the MoveToChain race fixed, the per-frame "send while moving" cadence is no longer load-bearing. Reverted to retail's two-branch ShouldSendPositionEvent gate (acclient_2013_pseudo_c.txt:700233-700285): Interval NOT elapsed (< 1 sec): send if cell or contact-plane changed. Interval elapsed (>= 1 sec): send if cell or position frame changed. Adds _lastSentContactPlane field + ApproxPlaneEqual helper + PlayerMovementController.ContactPlane public accessor. Extended NotePositionSent(Vector3, uint, Plane, float) — both outbound sites (MoveToState + AP) pass _playerController.ContactPlane. Effective rates: 0 Hz idle, ~1 Hz smooth motion, per-event on cell/plane changes, 0 Hz airborne. CLAUDE.md updated with no-workarounds rule (commit `da126f9` on the worktree branch). Saved as feedback memory at memory/feedback_no_workarounds.md. Tests: build green; Core.Net 294/294; Core 1073/1081 (baseline, 8 pre-existing Physics failures unchanged). Visual-verified end-to-end on 2026-05-16 for far/near Use + PickUp on NPCs, doors, items, spell components, signs (correctly blocked), corpses, turn-first animation, run/walk thresholds, idle quiet, smooth- motion 1Hz. Spec: docs/superpowers/specs/2026-05-16-phase-b6-suppress-movetostate-during-inbound-autowalk-design.md Plan: docs/superpowers/plans/2026-05-16-phase-b6-suppress-movetostate-during-inbound-autowalk.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b5da17db76
commit
d640ed74e1
6 changed files with 1317 additions and 200 deletions
|
|
@ -46,13 +46,68 @@ Copy this block when adding a new issue:
|
|||
|
||||
# Active issues
|
||||
|
||||
## #74 — AP cadence is per-frame-while-moving, more chatty than retail
|
||||
## #75 — [DONE 2026-05-16 · `f035ea3`] Auto-walk should drive body directly, not synthesize player-input
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** DONE
|
||||
**Severity:** LOW (functionally correct via grace-period band-aid; architectural cleanup only)
|
||||
**Filed:** 2026-05-16
|
||||
**Component:** physics / auto-walk
|
||||
|
||||
**Resolution (2026-05-16 · `f035ea3`):** Refactored `ApplyAutoWalkOverlay` → `DriveServerAutoWalk`. Auto-walk now steps Yaw, sets `_body.set_local_velocity` from runRate, and calls `_motion.DoMotion(WalkForward, speed)` directly — NO `MovementInput` synthesis. `Update` gates the user-input motion + velocity section on `!autoWalkConsumedMotion` to prevent overwrite. The 500ms arrival grace period (band-aid) deleted. The wire-layer `!IsServerAutoWalking` guard at `GameWindow.cs:6419` retained as a semantic statement (user-MoveToState is for user-driven intent only), not as a band-aid for the synthesis leak that no longer exists. Animation cycle plumbed through via `localAnimCmd` / `localAnimSpeed` for both moving-forward and turn-first phases (issue #69 folded in). Walk/run threshold corrected to 1.0m (overrides ACE's wire-supplied 15.0f; matches user-observed retail behaviour + ACE's own physics layer default). `IsPickupableTarget` now checks `BF_STUCK` (`acclient.h:6435`) to correctly block signs/banners that share Misc ItemType with real pickup items.
|
||||
|
||||
**Description:** `ApplyAutoWalkOverlay` in `PlayerMovementController`
|
||||
synthesizes `Forward+Run` `MovementInput` during inbound `MoveToObject`
|
||||
so the existing motion-interpreter pipeline drives the body. The
|
||||
synthesis leaks: motion-interpreter sets `MotionStateChanged=true`,
|
||||
which would fire an outbound `MoveToState` "user is running"
|
||||
packet to ACE — interpreted as user-took-manual-control and cancels
|
||||
ACE's `MoveToChain`. We mitigate with a guard
|
||||
(`!_playerController.IsServerAutoWalking` at `GameWindow.cs:6410`)
|
||||
plus a 500 ms post-arrival grace period to cover ACE's poll race.
|
||||
|
||||
Retail's `MoveToManager::HandleMoveToPosition` (decomp 0x0052xxxx)
|
||||
steps the body POSITION directly when server `MoveToObject` arrives —
|
||||
NO player-input synthesis, NO motion-interpreter involvement, NO
|
||||
outbound MoveToState. Holtburger
|
||||
([simulation.rs:178-206](references/holtburger/crates/holtburger-core/src/client/simulation.rs))
|
||||
follows the same pattern (sets `ServerControlledProjection`, advances
|
||||
the body, returns empty).
|
||||
|
||||
**Acceptance:** Refactor auto-walk to step `_body.Position` (or
|
||||
equivalent) directly from the wire-supplied path data + run rate, NOT
|
||||
via synthesized input. Motion state during auto-walk becomes a
|
||||
SERVER-DRIVEN state (similar to how remote players' motion is driven
|
||||
by inbound MoveToState packets), not a USER-DRIVEN one. The 500 ms
|
||||
grace period in `EndServerAutoWalk` becomes unnecessary and can be
|
||||
deleted; same for the `IsServerAutoWalking` guard at the wire layer
|
||||
(no MoveToState would have been built in the first place).
|
||||
|
||||
Animation cycle currently driven by motion-interpreter's
|
||||
`MotionStateChanged → SetCycle(RunForward)` would need a separate
|
||||
path: probably mirror how remote-player animation is driven by
|
||||
inbound motion packets (the sequencer accepts a `SetCycle` directly).
|
||||
|
||||
**Files:** `src/AcDream.App/Input/PlayerMovementController.cs`
|
||||
(`ApplyAutoWalkOverlay` returns synthesized input today; refactor to
|
||||
step body directly + drive animation via `_animationSequencer.SetCycle`
|
||||
directly). `src/AcDream.App/Rendering/GameWindow.cs` (delete the
|
||||
`!IsServerAutoWalking` guard once the leak is gone).
|
||||
|
||||
**Estimated scope:** Medium (~50-100 LOC + careful testing of
|
||||
animation cycle continuity). Not blocking M1 — the grace-period
|
||||
band-aid produces retail-faithful behaviour empirically.
|
||||
|
||||
---
|
||||
|
||||
## #74 — [DONE 2026-05-16 · `de44358`] AP cadence is per-frame-while-moving, more chatty than retail
|
||||
|
||||
**Status:** DONE
|
||||
**Severity:** LOW (works; just sends ~60× the packets retail would during smooth motion)
|
||||
**Filed:** 2026-05-16
|
||||
**Component:** physics / net cadence
|
||||
|
||||
**Resolution (2026-05-16 · `de44358`):** With #75 (MoveToState suppression refactor) closing the MoveToChain-cancellation race, the per-frame "send while moving" cadence is no longer load-bearing. Reverted to retail's two-branch `ShouldSendPositionEvent` gate (`acclient_2013_pseudo_c.txt:700233-700285`): cell/plane change during the sub-interval; cell-or-frame change after the 1s heartbeat. Added `_lastSentContactPlane` field + extended `NotePositionSent(Vector3, uint, Plane, float)` + added `ApproxPlaneEqual` helper + `PlayerMovementController.ContactPlane` public accessor. Effective rates now match retail: 0 Hz idle, ~1 Hz smooth motion, per-event on cell/plane changes, 0 Hz airborne.
|
||||
|
||||
**Description:** The diff-driven AP cadence shipped in Commit B fires
|
||||
`HeartbeatDue` on **any** position change each frame while grounded
|
||||
on walkable (effective ~60 Hz during smooth movement) and a 1 Hz
|
||||
|
|
@ -214,13 +269,15 @@ Not blocking M1.
|
|||
|
||||
---
|
||||
|
||||
## #69 — Local player rotation isn't animated (no leg/arm cycle while pivoting)
|
||||
## #69 — [DONE 2026-05-16 · `f035ea3`] Local player rotation isn't animated (no leg/arm cycle while pivoting)
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** DONE
|
||||
**Severity:** LOW (visual polish — rotation works, just looks stiff)
|
||||
**Filed:** 2026-05-15 (B.6 close-range turn-to-face)
|
||||
**Component:** motion / animation cycle
|
||||
|
||||
**Resolution (2026-05-16 · `f035ea3`):** Fixed as part of the auto-walk architectural refactor (issue #75). `DriveServerAutoWalk` now records the per-frame rotation direction in `_autoWalkTurnDirectionThisFrame` (+1 / -1 / 0); the animation override at the bottom of `Update` reads that flag and sets `localAnimCmd` to `TurnLeft` / `TurnRight` during the turn-first phase. User confirmed 2026-05-16 that the auto-walk turn-first case (click target, body rotates before walking) now plays the leg-shuffle animation. User-driven A/D rotation was always working — the original issue description was specific to the auto-walk turn-first case.
|
||||
|
||||
**Description:** When the auto-walk overlay rotates the local player
|
||||
(close-range Use turn-to-face, or turn-first phase of a far-range walk),
|
||||
the body's Yaw rotates smoothly but no leg / arm animation plays —
|
||||
|
|
@ -422,14 +479,20 @@ locally on send (mirroring retail's client behavior).
|
|||
|
||||
---
|
||||
|
||||
## #63 — Server-initiated auto-walk (MoveToObject) not honored
|
||||
## #63 — [DONE 2026-05-16 · `f035ea3`] Server-initiated auto-walk (MoveToObject) not honored
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** DONE
|
||||
**Severity:** MEDIUM (blocks out-of-range Use + Pickup; close-range
|
||||
works fine)
|
||||
**Filed:** 2026-05-14 (B.5 visual verification)
|
||||
**Component:** motion / inbound MoveToObject handling
|
||||
|
||||
**Resolution (2026-05-16):** Closed in two parts:
|
||||
1. **B.6 slice 2 (2026-05-14):** inbound MoveToObject parsing + `BeginServerAutoWalk` wiring at `GameWindow.cs:3389` — body auto-walks toward the server-supplied destination.
|
||||
2. **B.6 #75 refactor (`f035ea3`, 2026-05-16):** `ApplyAutoWalkOverlay → DriveServerAutoWalk` drives the body directly from path data, no input synthesis. The `MoveToState` leak that previously cancelled ACE's `MoveToChain` callback is gone; the chain runs uninterrupted and `TryUseItem` / `TryPickUp` fires server-side on arrival. No client-side retry needed. Walk/run threshold corrected to 1.0m (matches retail-observed; overrides ACE's wire-default 15m).
|
||||
|
||||
Visual-verified end-to-end: far-range Use on NPCs / doors / spell components / corpses all complete via ACE's server-side callback. The far-range retry workaround from Task 1's first iteration (`c61d049`'s `_pendingPostArrivalAction` arming) was deleted as part of #75 (`f035ea3`).
|
||||
|
||||
**Description:** When the player triggers a Use or PutItemInContainer
|
||||
on a target outside ACE's `WithinUseRadius` (default 0.6 m), ACE
|
||||
runs server-side auto-walk via `CreateMoveToChain` →
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue