10 Hz heartbeat (301281d) made ACE see us in-radius before its
MoveToChain timeout; user confirmed doors work now. Closing #67 — root
cause was 1 Hz position outbound on our side, not anything door-
specific. Same fix unblocked door + NPC.
Two visible refinements:
1. Turn-first gate. User report: 'when I use from far range, I should
face that object and then start moving. Now it starts running
before facing is complete.'
ApplyAutoWalkOverlay now suppresses Forward motion when the
heading delta to the target is > 30°. Body turns IN PLACE first,
then walks forward once roughly aligned. Within the 30° band the
body walks while finishing the residual turn. Matches the user-
observed retail rhythm.
2. Arrival margin shrunk 0.2 m → 0.05 m. User report: 'NPC dialogue
fires, but still a bit too close. In retail it fires from a longer
range.' With the 10 Hz heartbeat the server-side Player.Location
tracks us within ~100 ms, so the bigger safety margin is no longer
needed — only a tiny epsilon to absorb the sub-tick race between
local arrival fire and the next outbound packet.
Filed #68: remote players' running animation doesn't transition to
Ready on auto-walk arrival when observed from acdream. Separate
visual bug — server-side action completes correctly; just the cycle
on the dead-reckoned remote body doesn't flip back to idle.
This commit is contained in:
parent
301281d8d0
commit
32352af583
2 changed files with 62 additions and 37 deletions
|
|
@ -46,35 +46,44 @@ Copy this block when adding a new issue:
|
|||
|
||||
# Active issues
|
||||
|
||||
## #67 — Door Use action doesn't complete after auto-walk arrival
|
||||
## #68 — Remote players don't stop running animation on auto-walk arrival
|
||||
|
||||
**Status:** OPEN
|
||||
**Severity:** MEDIUM (M1-affecting — doors are M1 demo target 2)
|
||||
**Severity:** LOW-MEDIUM (visual only — server-side action completes correctly)
|
||||
**Filed:** 2026-05-15 (B.7 visual verification)
|
||||
**Component:** net / interaction
|
||||
**Component:** motion / remote dead-reckoning / animation cycle
|
||||
|
||||
**Description:** After B.6's auto-walk + B.7's flush-AP-and-re-send, NPC
|
||||
dialogue fires correctly on out-of-range Use. But Use on a door from
|
||||
out-of-range still doesn't open the door — the player walks to the
|
||||
door, sends the re-send, and nothing happens.
|
||||
**Description:** Observing a retail player from acdream as they approach
|
||||
an NPC at a distance: the remote body's run animation keeps cycling
|
||||
even after the body has visibly stopped at the NPC. Retail-side the
|
||||
character stopped; the action (dialogue) fired; but our client's
|
||||
animation never transitioned RunForward → Ready.
|
||||
|
||||
**Suspected causes (need investigation):**
|
||||
1. Door's `objDist` on the wire might be different from what we
|
||||
parse, so the local arrival lands outside ACE's actual
|
||||
door-use radius.
|
||||
2. ACE's `Door.ActOnUse` may require additional client-side state
|
||||
(specific stance, motion, facing) that our re-sent Use lacks.
|
||||
3. The door's ObjectDescriptionFlags `BF_DOOR (0x1000)` may not
|
||||
be properly captured / forwarded for our picker's per-type
|
||||
radius logic to fire.
|
||||
**Suspected:** `RemoteMoveToDriver` detects arrival via
|
||||
`DriveResult.Arrived`, but the consumer site (per-tick loop in
|
||||
`GameWindow.TickAnimations` or wherever the remote body's cycle is
|
||||
driven) doesn't flip the animation cycle back to Ready on arrival.
|
||||
Alternatively the cycle persists because ACE doesn't broadcast a
|
||||
follow-up `UpdateMotion(Ready)` — relying on the client to detect
|
||||
arrival from the wire's distance threshold instead.
|
||||
|
||||
**Acceptance:** F or R-key on a selected door from > 0.6 m runs the
|
||||
auto-walk, arrives, opens the door (player walks through; B.4c swing
|
||||
animation plays).
|
||||
**Files (likely):**
|
||||
- `src/AcDream.App/Rendering/GameWindow.cs` — wherever per-tick motion
|
||||
for remote entities reads `RemoteMoveToDriver`'s state. Need to
|
||||
call `SetCycle(NonCombat, Ready)` on arrival.
|
||||
|
||||
**Files (likely):** `src/AcDream.App/Rendering/GameWindow.cs`
|
||||
`SendUse`; `src/AcDream.App/Input/PlayerMovementController.cs`
|
||||
arrival; `references/ACE/Source/ACE.Server/WorldObjects/Door.cs`.
|
||||
**Acceptance:** Retail player observed running up to an NPC visibly
|
||||
stops running animation at arrival distance, transitions to idle.
|
||||
|
||||
---
|
||||
|
||||
## #67 — [DONE 2026-05-15 · `301281d`] Door Use action doesn't complete after auto-walk arrival
|
||||
|
||||
**Status:** DONE — fixed by `301281d` (10 Hz heartbeat during motion).
|
||||
With ACE seeing our position in near-real-time, its `CreateMoveToChain`
|
||||
converges normally for doors as well as NPCs. Root cause was 1 Hz
|
||||
position sync on our side, not anything door-specific. User confirmed
|
||||
doors work after the heartbeat bump.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -445,22 +445,17 @@ public sealed class PlayerMovementController
|
|||
float dy = _autoWalkDestination.Y - pos.Y;
|
||||
float dist = MathF.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Arrival predicate. CRITICAL: ACE's server-side WithinUseRadius
|
||||
// is strict (dist <= radius), so arriving exactly at the radius
|
||||
// boundary fails — ACE rejects the action and replies with
|
||||
// another MoveToObject. We walk slightly INSIDE the boundary so
|
||||
// the re-sent action lands safely in-range.
|
||||
//
|
||||
// The margin is small — user feedback says retail fires Use
|
||||
// from longer range, so we minimise the over-walk: 0.2 m at
|
||||
// typical NPC radii (3 m → arrive at 2.8 m), tapered for tight
|
||||
// pickup radii (0.6 m → arrive at 0.48 m) so the body stays
|
||||
// reachable but always inside ACE's strict check.
|
||||
// Arrival predicate. With the 10 Hz heartbeat from 301281d the
|
||||
// server-side Player.Location tracks our body within ~100 ms, so
|
||||
// the previous "subtract 0.2 m safety margin" workaround is no
|
||||
// longer needed. Tiny 0.05 m margin remains to absorb the
|
||||
// sub-tick race between local arrival-fire and the next
|
||||
// heartbeat's outbound packet.
|
||||
float arrivalThreshold = _autoWalkMoveTowards
|
||||
? _autoWalkDistanceToObject
|
||||
: _autoWalkMinDistance;
|
||||
float safetyMargin = MathF.Min(0.2f, arrivalThreshold * 0.2f);
|
||||
float effectiveArrival = MathF.Max(arrivalThreshold - safetyMargin, 0.1f);
|
||||
const float TinyMargin = 0.05f;
|
||||
float effectiveArrival = MathF.Max(arrivalThreshold - TinyMargin, 0.1f);
|
||||
bool arrived =
|
||||
(_autoWalkMoveTowards
|
||||
&& dist <= effectiveArrival)
|
||||
|
|
@ -476,6 +471,14 @@ public sealed class PlayerMovementController
|
|||
// _body.Orientation = Quaternion.CreateFromAxisAngle(Z, Yaw - π/2),
|
||||
// so local-forward (+Y) maps to world (cos Yaw, sin Yaw, 0).
|
||||
// Therefore Yaw that faces (dx,dy) is atan2(dy, dx).
|
||||
//
|
||||
// User feedback (2026-05-15): 'I should face that object and then
|
||||
// start moving. Now it starts running before facing is complete.'
|
||||
// Track the current heading delta — if we're more than the
|
||||
// walk-while-turning threshold off, suppress Forward this frame
|
||||
// so the body turns IN PLACE first. Once we're within the
|
||||
// threshold, the synthesised Forward+Run kicks in below.
|
||||
bool aligned = true;
|
||||
if (dist > 1e-4f)
|
||||
{
|
||||
float desiredYaw = MathF.Atan2(dy, dx);
|
||||
|
|
@ -493,6 +496,14 @@ public sealed class PlayerMovementController
|
|||
}
|
||||
while (Yaw > MathF.PI) Yaw -= 2f * MathF.PI;
|
||||
while (Yaw < -MathF.PI) Yaw += 2f * MathF.PI;
|
||||
|
||||
// 30° "walk-while-turning" threshold: outside this, body
|
||||
// turns in place. Inside, body walks forward while finishing
|
||||
// any remaining alignment. Matches retail-feel observation;
|
||||
// exact retail value is in MoveToManager but ~30° is a
|
||||
// sensible heuristic for now.
|
||||
const float WalkWhileTurningRad = 30f * MathF.PI / 180f;
|
||||
aligned = MathF.Abs(delta) <= WalkWhileTurningRad;
|
||||
}
|
||||
|
||||
// Walk vs run decided ONCE at BeginServerAutoWalk based on
|
||||
|
|
@ -503,6 +514,11 @@ public sealed class PlayerMovementController
|
|||
// all the way to the object and then stop").
|
||||
bool shouldRun = _autoWalkInitiallyRunning;
|
||||
|
||||
// Turn-first gate: if not yet aligned with the target, suppress
|
||||
// forward motion so the body turns in place rather than
|
||||
// walking an arc.
|
||||
bool moveForward = aligned;
|
||||
|
||||
// Synthesize "moving forward" input. The rest of Update reads
|
||||
// Yaw + input.Forward + input.Run to drive _motion + body
|
||||
// velocity exactly as it does for user-driven W (+ optional Shift).
|
||||
|
|
@ -510,8 +526,8 @@ public sealed class PlayerMovementController
|
|||
// steering.
|
||||
return input with
|
||||
{
|
||||
Forward = true,
|
||||
Run = shouldRun,
|
||||
Forward = moveForward,
|
||||
Run = moveForward && shouldRun,
|
||||
Backward = false,
|
||||
StrafeLeft = false,
|
||||
StrafeRight = false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue