docs(B.6): retail decomp settles Option A; revise spec with 4-slice plan
Grounded the design in named-retail evidence. MovementManager::Perform
Movement at 0x00524440 case 6 (decomp lines 300628-300648) shows the
retail client's local-side dispatcher for inbound MoveToObject:
unpacks the wire, sets motion_interpreter->my_run_rate, calls
CPhysicsObj::MoveToObject on the LOCAL player's physics body. Same
code path retail used for every creature chasing the player.
Conclusion: Option A (run a local driver against the player's body)
is retail-faithful. Option C (server-position-blend) is a non-retail
shortcut and is now eliminated from consideration.
Re-scoped the spec into 4 slices:
1. ACDREAM_PROBE_AUTOWALK diagnostic baseline (~30 LOC)
2. PlayerMovementController.BeginServerAutoWalk + reuse of
RemoteMoveToDriver against the local player's body (~100 LOC)
3. Animation cycle selection during auto-walk (~20 LOC)
4. Local pickup-animation echo (closes #64, ~10 LOC)
Total ~160 LOC, no new files. All existing acdream infrastructure
(RemoteMoveToDriver, ServerControlledLocomotion, MotionState.MoveTo
Path parsing) is reused; the work is wiring it for _playerServerGuid
in addition to remote guids.
This commit is contained in:
parent
281d125e9b
commit
9e1d33a5f7
1 changed files with 142 additions and 54 deletions
|
|
@ -164,22 +164,73 @@ ACE's broadcast cadence is too sparse, the motion is choppy.
|
||||||
|
|
||||||
## Recommendation
|
## Recommendation
|
||||||
|
|
||||||
Start with **Option C** for the minimum viable fix; promote to
|
**Option A is the retail-faithful path; Options B and C are non-retail
|
||||||
**Option A** if Option C produces choppy/janky motion. Skip Option B
|
shortcuts. Implement Option A.**
|
||||||
unless A and C both prove harder than they look.
|
|
||||||
|
|
||||||
**Why C first.**
|
### Retail evidence (settles A vs C)
|
||||||
- Smallest blast radius. Can be a single-commit hotfix.
|
|
||||||
- Reveals exactly what ACE is sending in practice (the diagnostic
|
|
||||||
layer below tells us cadence + format).
|
|
||||||
- If Option C works smoothly, B.6 is done in a single session.
|
|
||||||
- If Option C is too choppy, the diagnostic data informs Option A's
|
|
||||||
design — what cadence does the per-tick driver need to fill in
|
|
||||||
between server-position broadcasts?
|
|
||||||
|
|
||||||
**Why not B.** Tweens aren't retail-faithful; we'd carry a
|
`MovementManager::PerformMovement` at retail address `0x00524440`
|
||||||
non-retail visual band-aid into M2 and have to revisit when combat
|
(decomp `docs/research/named-retail/acclient_2013_pseudo_c.txt` lines
|
||||||
movement starts depending on real physics paths.
|
300628–300648) is the inbound-motion dispatcher. The switch on movement
|
||||||
|
type has explicit cases for `MoveToObject (6)` and `MoveToPosition (7)`
|
||||||
|
that:
|
||||||
|
|
||||||
|
1. Call `MovementManager::MakeMoveToManager(this)` to ensure the
|
||||||
|
per-physics-object manager exists.
|
||||||
|
2. Unpack the target guid + origin position + `MovementParameters` from
|
||||||
|
the wire.
|
||||||
|
3. Set `this->motion_interpreter->my_run_rate` from the packet.
|
||||||
|
4. Call **`CPhysicsObj::MoveToObject(this->physics_obj, target_guid,
|
||||||
|
¶ms)`** — which kicks off a **fully local** auto-walk on the
|
||||||
|
player's own physics body (`0x00512860`, decomp line 280598).
|
||||||
|
5. Fall through to `MoveToManager::MoveToPosition` only if the target
|
||||||
|
guid isn't found in the local physics world (rare; usually means the
|
||||||
|
client hasn't streamed in the target yet).
|
||||||
|
|
||||||
|
This is the **same code path** that runs for every remote creature
|
||||||
|
chasing the player — retail did not have a separate "remote-only" vs
|
||||||
|
"local-only" auto-walk pipeline. The local client's MoveToManager
|
||||||
|
actively drove the local player's body forward when the server sent
|
||||||
|
MoveToObject. ACE's server-side `PhysicsObj.MoveToObject` simulation
|
||||||
|
runs in parallel for authoritative-position tracking + arrival
|
||||||
|
detection (`MoveToChain.WithinUseRadius`), but the visible movement on
|
||||||
|
the local client comes from the local MoveToManager — not from
|
||||||
|
inbound UpdatePosition packets.
|
||||||
|
|
||||||
|
Option C would diverge from retail by relying on server position
|
||||||
|
broadcasts instead of local physics integration. That risks combat
|
||||||
|
movement, environment-trigger interactions, and animation hooks all
|
||||||
|
diverging from retail because they'd be driven by sparse server-side
|
||||||
|
position snapshots rather than smooth local integration.
|
||||||
|
|
||||||
|
### Existing acdream infrastructure that's already retail-shaped
|
||||||
|
|
||||||
|
We have most of the building blocks already:
|
||||||
|
|
||||||
|
- [`RemoteMoveToDriver`](../../../src/AcDream.Core/Physics/RemoteMoveToDriver.cs)
|
||||||
|
is the per-tick steering loop — heading correction, arrival via
|
||||||
|
`min_distance` / `distance_to_object`, ±20° aux-turn tolerance —
|
||||||
|
ported from retail's `MoveToManager::HandleMoveToPosition`
|
||||||
|
(`0x00529d80`). It's already exercised on every NPC chase. The
|
||||||
|
retail-faithful fix for the local player **reuses this driver**,
|
||||||
|
installed against the local player's body instead of a remote's
|
||||||
|
dead-reckoned body.
|
||||||
|
- [`ServerControlledLocomotion.PlanMoveToStart`](../../../src/AcDream.Core/Physics/ServerControlledLocomotion.cs)
|
||||||
|
already does what retail's `MovementParameters::get_command`
|
||||||
|
(`0x0052AA00`) does: seed `WalkForward` / `RunForward` depending on
|
||||||
|
`CanRun` + `MoveToSpeed` + `MoveToRunRate`.
|
||||||
|
- `MotionState.MoveToPath` is fully parsed on the wire. Remote chase
|
||||||
|
reads it at `GameWindow.cs:3401–3417`.
|
||||||
|
|
||||||
|
The B.6 work is essentially **"do for `_playerServerGuid` what we
|
||||||
|
already do for remotes,"** with one extra concern: the local player has
|
||||||
|
a user-input motion source (`PlayerMovementController`) that has to
|
||||||
|
yield to the auto-walk while it's active.
|
||||||
|
|
||||||
|
### Why not B
|
||||||
|
|
||||||
|
Tweens aren't retail-faithful and would diverge worse than C.
|
||||||
|
Eliminated.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -201,45 +252,72 @@ characterized in detail. We need a live trace of:
|
||||||
local-player auto-walk attempts. Roughly 30 LOC; mirrors L.2a slice 1's
|
local-player auto-walk attempts. Roughly 30 LOC; mirrors L.2a slice 1's
|
||||||
`ACDREAM_PROBE_RESOLVE` / `ACDREAM_PROBE_CELL` pattern.
|
`ACDREAM_PROBE_RESOLVE` / `ACDREAM_PROBE_CELL` pattern.
|
||||||
|
|
||||||
The first 30 minutes of the next B.6 session should produce a clean
|
The trace is no longer needed to *decide between options* (retail
|
||||||
trace of a single failed auto-walk attempt, from outbound packet
|
decomp settles that — Option A wins), but it remains valuable as a
|
||||||
through ACE's `ActionCancelled`. The trace decides between Option A
|
**baseline measurement** for the Option A implementation: knowing what
|
||||||
and Option C.
|
ACE sends today lets us verify the local driver behaves equivalently
|
||||||
|
on the wire (no extra packets needed, position broadcasts arrive at
|
||||||
|
expected cadence, the auto-walk completes inside
|
||||||
|
`defaultMoveToTimeout` instead of timing out).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## File-level scope sketch (Option C path)
|
## File-level scope sketch (Option A — retail-faithful)
|
||||||
|
|
||||||
If the trace confirms ACE broadcasts adequate `UpdatePosition` during
|
Mirror retail's `MovementManager::PerformMovement` case 6 against
|
||||||
auto-walk:
|
acdream's existing `PlayerMovementController` + `RemoteMoveToDriver`.
|
||||||
|
|
||||||
- **Modify:** [`src/AcDream.Core/Physics/PlayerMovementController.cs`](../../../src/AcDream.Core/Physics/PlayerMovementController.cs)
|
**Slice 1 — diagnostic baseline (~30 LOC).**
|
||||||
— add an `IsServerAutoWalking` flag + setter. While true, suppress
|
- **Modify:** `src/AcDream.Core/Physics/PhysicsDiagnostics.cs` — add
|
||||||
the user-input snap-back path in the reconciliation loop.
|
`ProbeAutoWalkEnabled` flag gated on `ACDREAM_PROBE_AUTOWALK=1`.
|
||||||
- **Modify:** [`src/AcDream.App/Rendering/GameWindow.cs`](../../../src/AcDream.App/Rendering/GameWindow.cs)
|
- **Modify:** `GameWindow.OnLiveMotionUpdated`, `OnLivePositionUpdated`,
|
||||||
`OnLiveMotionUpdated` — when `update.Guid == _playerServerGuid` and
|
`OnVectorUpdated`, `SendUse`, `SendPickUp` — when probe is on and the
|
||||||
`IsServerControlledMoveTo`, set the flag on the controller. When a
|
guid is `_playerServerGuid`, log one line per event with timestamp +
|
||||||
non-MoveTo motion arrives for the player (Ready, RunForward initiated
|
payload. Mirror the `[resolve]` / `[cell-transit]` line format from
|
||||||
by user, etc.), clear it.
|
L.2a.
|
||||||
- **Modify:** `GameWindow.OnLivePositionUpdated` (or equivalent) — while
|
|
||||||
the auto-walk flag is set, trust server position without
|
|
||||||
reconciliation.
|
|
||||||
|
|
||||||
Total: estimated ~60 LOC + diagnostic.
|
**Slice 2 — install auto-walk on inbound MoveToObject (~100 LOC).**
|
||||||
|
- **Modify:** `PlayerMovementController` — add `BeginServerAutoWalk(
|
||||||
|
Vector3 destinationWorld, float minDistance, float distanceToObject,
|
||||||
|
bool moveTowards, float moveToSpeed, float moveToRunRate, bool
|
||||||
|
canRun)` + `EndServerAutoWalk(reason)` methods. The controller owns
|
||||||
|
the "input vs auto-walk" state. While auto-walking, the per-tick
|
||||||
|
update calls `RemoteMoveToDriver.Step(...)` against its own body, and
|
||||||
|
the user-input motion path is suppressed.
|
||||||
|
- **Modify:** `GameWindow.OnLiveMotionUpdated` — when `update.Guid ==
|
||||||
|
_playerServerGuid && IsServerControlledMoveTo && MoveToPath is
|
||||||
|
not null`, translate the path's `OriginCellId + OriginXYZ` to world
|
||||||
|
space (same `RemoteMoveToDriver.OriginToWorld` helper the remote
|
||||||
|
path uses), call `_playerController.BeginServerAutoWalk(...)`.
|
||||||
|
Otherwise (a non-MoveTo motion arrives for the player), call
|
||||||
|
`EndServerAutoWalk(reason="motion-changed")`.
|
||||||
|
- **Modify:** `PlayerMovementController.Tick` — if auto-walking,
|
||||||
|
consume input only for cancellation (W/A/S/D pressed → cancel
|
||||||
|
auto-walk → restore input); skip the input-driven velocity solve;
|
||||||
|
let `RemoteMoveToDriver.Step` set the body's velocity + heading;
|
||||||
|
apply arrival check via `min_distance` / `distance_to_object`; on
|
||||||
|
arrival, call `EndServerAutoWalk(reason="arrived")`.
|
||||||
|
|
||||||
## File-level scope sketch (Option A path, if needed)
|
**Slice 3 — animation cycle selection (~20 LOC).**
|
||||||
|
- **Modify:** `GameWindow` `UpdatePlayerAnimation` (the path that
|
||||||
|
drives the local player's animation cycle from user input) — when
|
||||||
|
the controller is in `IsServerAutoWalking` state, source the cycle
|
||||||
|
from `ServerControlledLocomotion.PlanMoveToStart(...)` instead of the
|
||||||
|
user-input MotionInterpreter. This is what makes the local player
|
||||||
|
visibly walk + animate during the auto-walk.
|
||||||
|
|
||||||
- All of Option C's changes, plus:
|
**Slice 4 — local pickup animation (probably closes #64, ~10 LOC).**
|
||||||
- **New:** `src/AcDream.Core/Physics/LocalAutoWalkDriver.cs` — port the
|
- **Modify:** `OnLiveMotionUpdated` — for `_playerServerGuid`, allow
|
||||||
`RemoteMoveToDriver` logic adapted for the local player's body, including
|
`Motion(Pickup)` / `Motion(Pickup5/10/15/20)` to drive `SetCycle`
|
||||||
arrival detection + cycle selection.
|
bypassing the existing self-echo filter at `GameWindow.cs:3289`.
|
||||||
- **Modify:** `PlayerMovementController` — integrate the driver into the
|
This is the bend-down animation retail observers see when we pick up
|
||||||
per-tick update loop when auto-walking; suppress input-derived
|
an item. Same one-shot mechanism retail used; `UpdatePlayerAnimation`
|
||||||
velocity while active.
|
doesn't predict it because it's server-initiated, so admitting the
|
||||||
- **Modify:** `GameWindow.OnLiveMotionUpdated` — wire the driver
|
echo is correct.
|
||||||
installation + teardown.
|
|
||||||
|
|
||||||
Total: estimated ~150–250 LOC including tests.
|
Total: estimated ~160 LOC + unit tests for the controller state
|
||||||
|
machine. No new files; all changes land in existing physics + render
|
||||||
|
infrastructure.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -281,18 +359,28 @@ Total: estimated ~150–250 LOC including tests.
|
||||||
|
|
||||||
## Carry-overs from B.5
|
## Carry-overs from B.5
|
||||||
|
|
||||||
- **#64** — local-player pickup animation. Likely related: same self-
|
- **#64** — local-player pickup animation. Same self-echo filter at
|
||||||
echo filter at `OnLiveMotionUpdated:3289` that ignores MoveToObject
|
`OnLiveMotionUpdated:3289` that ignores MoveToObject also drops the
|
||||||
also drops the inbound `Motion(Pickup)` that retail observers render
|
inbound `Motion(Pickup)` retail observers render correctly. Slice 4
|
||||||
correctly. May fix in the same B.6 work, or as a follow-up depending
|
above admits server-initiated one-shot motions through the filter
|
||||||
on how the auto-walk fix shakes out.
|
for the local player, which should close #64. Verify in the same
|
||||||
|
visual-test pass.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## State at design freeze
|
## State at design freeze
|
||||||
|
|
||||||
- **Main HEAD:** `5053e40` (post-B.5 polish; M1 mechanically clean).
|
- **Main HEAD:** `281d125` (initial B.6 design spec committed).
|
||||||
- **No code changes in this commit** — design document only.
|
- **No code changes in this spec commit** — design document only.
|
||||||
- **Next session entry point:** add the `ACDREAM_PROBE_AUTOWALK`
|
- **Spec update 2026-05-14 (this commit):** retail decomp at
|
||||||
diagnostic, run a failed auto-walk reproduction, decide A vs C from
|
`MovementManager::PerformMovement` (`0x00524440` case 6, decomp lines
|
||||||
the trace.
|
300628–300648) decisively settles A vs C in favor of **Option A**.
|
||||||
|
Retail's local client ran its own `MoveToManager` and called
|
||||||
|
`CPhysicsObj::MoveToObject` on the local player's body. Option C
|
||||||
|
(server-position-blend) is not retail-faithful and is no longer
|
||||||
|
considered.
|
||||||
|
- **Next session entry point:** Slice 1 — add the
|
||||||
|
`ACDREAM_PROBE_AUTOWALK` diagnostic as the baseline, run a failed
|
||||||
|
auto-walk reproduction for a clean trace, then proceed to Slice 2
|
||||||
|
(`PlayerMovementController.BeginServerAutoWalk` + `RemoteMoveToDriver`
|
||||||
|
reuse for the local player).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue