feat(L.2g-S5): delete player pace-inference layer; close #39 (DEV-2)
S0 wire probe (live capture, retail actor via ACE) refuted the premise the #39 machinery was built on: walk<->run Shift toggles arrive as EXPLICIT UMs (0x0005 <-> 0x0007@runRate) because retail's default- difference packing baselines forward_command against Ready — W-held is always packed, and ACE re-emits it unconditionally with the holdKey upgrade (MovementData.cs:104-119). The frequent flags=0 autonomous UMs are genuine keys-released / heading-only states; retail applies them as full stops (InterpretedMotionState ctor 0x0051e8d0 defaults Ready + move_to_interpreted_state 0x005289c0 flat copy). Retail has NO pace->animation adaptation anywhere in its inbound pipeline (two decomp dives + ACE cross-check, deviation map DEV-2), and the refinement layer's 0.2s-grace re-promotion after legitimate Ready UMs was itself the observed Ready<->Run thrash / rubber-band component. Deleted: ApplyPlayerLocomotionRefinement, UmGraceSeconds, PlayerRunPromoteSpeed/PlayerRunDemoteSpeed, RemoteMotion.LastUMTime, the synth-player refinement call in OnLivePositionUpdated. Player-remote cycles are now UM-driven only, exactly like retail. NPC PlanFromVelocity path untouched (S6 unifies). S0 findings + S1 live validation (0 false UM_STALE drops across 280 UMs) recorded in docs/research/2026-07-02-inbound-motion-deviation-map.md. fix #39: closed — root-cause narrative corrected, machinery removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
cb74e64343
commit
a2f8104cbf
3 changed files with 87 additions and 274 deletions
|
|
@ -3108,7 +3108,20 @@ regression on the species that already render correctly.
|
|||
|
||||
## #39 — Run↔Walk cycle transition not visible on observed player remotes (acdream-as-observer)
|
||||
|
||||
**Status:** OPEN — VERIFY-PENDING (cases #1/#2/#4/#5 user-verified working 2026-05-06; cases #3/#6/#7 unverified in live test)
|
||||
**Status:** CLOSED 2026-07-02 (superseded by L.2g; refinement machinery DELETED in the S5 commit)
|
||||
**Closure note:** the root-cause narrative below is WRONG — the 2026-05-06
|
||||
"wire goes silent on Shift toggle" finding was refuted at three oracles + a
|
||||
fresh live capture (`docs/research/2026-07-02-inbound-motion-deviation-map.md`,
|
||||
S0 section): retail sends a fresh MoveToState on HoldRun toggle while moving
|
||||
(`CommandInterpreter` 0x006b37a8 → `SendMovementEvent`), ACE rebroadcasts every
|
||||
MoveToState (`GameActionMoveToState.cs:36`), and the S0 capture shows explicit
|
||||
`0x0005↔0x0007` UMs on each toggle. Retail has NO pace→cycle adaptation
|
||||
anywhere (DEV-2). The `ApplyPlayerLocomotionRefinement` layer this issue added
|
||||
was itself causing Ready↔Run thrash against legitimate flags=0 stop UMs and
|
||||
was deleted; player-remote cycles are UM-driven only. Remaining transition
|
||||
polish (funnel, stop path, link pose) is tracked as roadmap L.2g S2–S4.
|
||||
|
||||
**Original status:** OPEN — VERIFY-PENDING (cases #1/#2/#4/#5 user-verified working 2026-05-06; cases #3/#6/#7 unverified in live test)
|
||||
**Severity:** LOW (most cases now visibly correct after the 2026-05-06 fix sequence; remaining unverified cases are direction-flip — believed to work via direct UM but not explicitly exercised)
|
||||
**Filed:** 2026-05-03
|
||||
**Component:** physics / motion / animation
|
||||
|
|
|
|||
|
|
@ -258,6 +258,46 @@ bookkeeping (DEV-7), and stop path (DEV-3) can be ported around it surgically;
|
|||
and it is NOT a dead-reckoning-tuning problem — the chase is already retail,
|
||||
it's the inputs (cycle + velocity source + stop signal) that deviate.
|
||||
|
||||
## S0 wire-probe RESULTS (2026-07-02, post-S1 live capture)
|
||||
|
||||
Observer acdream (+Acdream), actor retail-driven (guid 0x5000000B), structured
|
||||
protocol incl. Shift toggles. 280 UMs captured (`launch-s0-wireprobe.log`).
|
||||
|
||||
1. **The wire is NOT silent — DEV-2's premise is dead.** Walk↔run toggles
|
||||
arrive explicitly: `fwd=0x0005 fwdSpd=null(=1.0)` ↔ `fwd=0x0007
|
||||
fwdSpd=2.85`. Root cause of the 2026-05-06 misreading: retail's
|
||||
default-difference packing baselines `forward_command` against **Ready**
|
||||
(`RawMotionState.Default`, our own D6.2b verbatim port) — W-held is
|
||||
non-default and always packed, so ACE (`MovementData.cs:104-119`) always
|
||||
re-emits it with the holdKey upgrade + run-skill speed.
|
||||
2. **flags=0 "empty" autonomous UMs are frequent (127/209) and LEGITIMATE**
|
||||
— they are genuine keys-released / heading-only MoveToState relays
|
||||
(all-default raw state packs nothing; ACE `RawMotionState.Read` leaves
|
||||
absent = Invalid; `MovementData` emits nothing). Retail semantics
|
||||
(verified: `InterpretedMotionState` ctor 0x0051e8d0 defaults
|
||||
`forward_command=Ready`; `MovementManager::unpack_movement` case 0
|
||||
UnPacks into a default-constructed state;
|
||||
`CMotionInterp::move_to_interpreted_state` 0x005289c0 does a FLAT
|
||||
`copy_movement_from` + `apply_current_movement`): an empty UM = a real
|
||||
full stop. acdream already maps it to Ready — but the DEV-2 refinement
|
||||
then re-promotes the cycle from UP pace after the 0.2 s grace, producing
|
||||
a Ready↔Run thrash against legitimate stop signals. That thrash is the
|
||||
observed flicker/rubber-band component.
|
||||
3. **S1 gate validated live**: 0 `[UM_STALE]` drops across 280 UMs;
|
||||
movementSeq advances +1 per UM; byte-level layout confirmed
|
||||
(`instanceSeq|movementSeq|serverControlSeq|isAutonomous` exactly where
|
||||
the parser reads them).
|
||||
4. Also confirmed in `unpack_movement` (0x00524440) for the S2 port: the
|
||||
outer style u16 routes through `command_ids[]` and applies via
|
||||
`DoMotion` ONLY when it differs from the current style; motionFlags
|
||||
carries sticky-object (0x100 → `stick_to_object`) and standing_longjump
|
||||
(0x200); the actions list is consumed under a 15-bit
|
||||
`server_action_stamp` wraparound compare
|
||||
(`move_to_interpreted_state` 0x005289c0, lines 305953-305989).
|
||||
|
||||
**Disposition: S5 = delete the player pace-inference layer** (executed in the
|
||||
same session; NPC `PlanFromVelocity` path untouched — its unification is S6).
|
||||
|
||||
## Provenance
|
||||
|
||||
- Workflow `wf_b92a5670-283`: 4 read-only mappers (retail decomp / ACE port /
|
||||
|
|
|
|||
|
|
@ -570,19 +570,6 @@ public sealed class GameWindow : IDisposable
|
|||
/// </summary>
|
||||
public float MaxSeqSpeedSinceLastUP;
|
||||
|
||||
/// <summary>
|
||||
/// Seconds-since-epoch timestamp of the most recent UpdateMotion (UM)
|
||||
/// for this remote. Used by the player-remote velocity-fallback cycle
|
||||
/// refinement to skip refinement while a fresh UM is authoritative —
|
||||
/// retail's outbound MoveToState gives us direction-explicit cycles
|
||||
/// on direction-key changes (W press, W release, W↔S flip), and we
|
||||
/// only want UP-derived velocity to refine the speed bucket within
|
||||
/// a direction when no UM has arrived recently. Defaults to 0
|
||||
/// (epoch) so the first UP after spawn is allowed to refine
|
||||
/// immediately if velocity already differs from the spawn cycle.
|
||||
/// </summary>
|
||||
public double LastUMTime;
|
||||
|
||||
public RemoteMotion()
|
||||
{
|
||||
Body = new AcDream.Core.Physics.PhysicsBody
|
||||
|
|
@ -4288,19 +4275,6 @@ public sealed class GameWindow : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
// #39 (2026-05-06): stamp the per-remote LastUMTime so the
|
||||
// UP-velocity fallback path in ApplyServerControlledVelocityCycle
|
||||
// can skip refinement while a UM is fresh. UMs are authoritative
|
||||
// for direction-key changes (W press / release / W↔S flip);
|
||||
// velocity refinement only helps for HoldKey-only changes (Shift
|
||||
// toggle while a direction key is held — retail does NOT broadcast
|
||||
// a fresh MoveToState in that case).
|
||||
if (_remoteDeadReckon.TryGetValue(update.Guid, out var rmStateForUm))
|
||||
{
|
||||
rmStateForUm.LastUMTime =
|
||||
(System.DateTime.UtcNow - System.DateTime.UnixEpoch).TotalSeconds;
|
||||
}
|
||||
|
||||
// Re-resolve using the new stance/command. Keep the setup and
|
||||
// motion-table we already know about — the server's motion
|
||||
// updates override state within the same table, not swap tables.
|
||||
|
|
@ -5135,35 +5109,6 @@ public sealed class GameWindow : IDisposable
|
|||
return low is 0x05 or 0x06 or 0x07 or 0x0F or 0x10;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grace window in seconds after a UM arrives during which UP-derived
|
||||
/// velocity refinement is suppressed for a player remote. UMs are
|
||||
/// authoritative; the velocity fallback only fills the gap when retail
|
||||
/// does not send a fresh MoveToState (Shift toggle while direction key
|
||||
/// held). 200 ms covers the worst-case UM/UP race — UMs arrive on
|
||||
/// direction-key events and UPs at 5–10 Hz, so the first UP after a
|
||||
/// fresh UM lands ~100–200 ms behind. Tightened from 500 ms (commit
|
||||
/// 8fa04af original) per user observation that the Shift-toggle
|
||||
/// transition was visibly slower than retail; with 0.2 s the worst-case
|
||||
/// added latency is just the UP cadence below it.
|
||||
/// </summary>
|
||||
private const double UmGraceSeconds = 0.2;
|
||||
|
||||
/// <summary>
|
||||
/// Speed (m/s) above which a player-remote currently in WalkForward
|
||||
/// is promoted to RunForward by velocity refinement. Tuned to player
|
||||
/// speeds: walk ≈ 3.12 m/s (WalkAnimSpeed × 1.0), run ≈ 8–12 m/s
|
||||
/// (RunAnimSpeed × runRate ≈ 4.0 × 2.0–3.0). Hysteresis with
|
||||
/// <see cref="PlayerRunDemoteSpeed"/> avoids thrashing at the boundary.
|
||||
/// </summary>
|
||||
private const float PlayerRunPromoteSpeed = 5.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Speed (m/s) below which a player-remote currently in RunForward
|
||||
/// is demoted to WalkForward by velocity refinement.
|
||||
/// </summary>
|
||||
private const float PlayerRunDemoteSpeed = 4.5f;
|
||||
|
||||
private void ApplyServerControlledVelocityCycle(
|
||||
uint serverGuid,
|
||||
AnimatedEntity ae,
|
||||
|
|
@ -5180,27 +5125,24 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
if (IsPlayerGuid(serverGuid))
|
||||
{
|
||||
// #39 (2026-05-06): player-remote forward-direction speed-bucket
|
||||
// refinement. The bug case: actor toggles Shift while holding W
|
||||
// (or releases Shift). Retail's outbound apparently does NOT
|
||||
// broadcast a fresh MoveToState for HoldKey-only changes
|
||||
// (verified via static analysis of CommandInterpreter::SendMovementEvent
|
||||
// call sites; needs cdb confirmation). ACE has nothing to
|
||||
// broadcast → no UM arrives at the observer → cycle stays at
|
||||
// whichever direction-bucket was last set. Velocity DOES change
|
||||
// (UP carries new pace), so this code path uses UP-derived
|
||||
// velocity to refine the speed bucket within the same direction.
|
||||
// L.2g S5 (2026-07-02, DEV-2 DELETED): player remotes get NO
|
||||
// pace-derived cycle refinement — retail has no such mechanism
|
||||
// anywhere in its inbound pipeline (deviation map DEV-2, two
|
||||
// independent decomp dives + ACE cross-check). The #39-era
|
||||
// premise ("retail's outbound goes silent on Shift toggle") was
|
||||
// refuted at all three oracles + the S0 live capture: retail
|
||||
// sends a fresh MoveToState on HoldRun toggle while moving
|
||||
// (CommandInterpreter 0x006b37a8 → SendMovementEvent), ACE
|
||||
// rebroadcasts every MoveToState (GameActionMoveToState.cs:36),
|
||||
// and the wire shows explicit 0x0005↔0x0007 UMs on each toggle
|
||||
// (launch-s0-wireprobe.log). The refinement layer's re-promote
|
||||
// after legitimate flags=0 Ready UMs was itself the observed
|
||||
// Ready↔Run thrash. Cycle changes for player remotes come from
|
||||
// UpdateMotion ONLY, exactly like retail; position error is the
|
||||
// InterpolationManager chase's job.
|
||||
//
|
||||
// Conservative scope:
|
||||
// - Forward direction only (low byte 0x05 or 0x07). Sidestep
|
||||
// and backward HoldKey toggles are deferred until the TTD
|
||||
// trace described in
|
||||
// docs/research/2026-05-06-locomotion-cycle-transitions/
|
||||
// confirms retail's exact algorithm.
|
||||
// - Hysteresis (4.5 m/s demote / 5.5 m/s promote) prevents
|
||||
// thrashing at the boundary.
|
||||
// - 500 ms UM grace window — a fresh UM is always authoritative.
|
||||
ApplyPlayerLocomotionRefinement(serverGuid, ae, rm, velocity);
|
||||
// NPC/monster remotes below keep PlanFromVelocity until S6
|
||||
// unifies all entity classes onto the CMotionInterp funnel.
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -5230,162 +5172,6 @@ public sealed class GameWindow : IDisposable
|
|||
ae.Sequencer.SetCycle(style, plan.Motion, plan.SpeedMod);
|
||||
}
|
||||
|
||||
private void ApplyPlayerLocomotionRefinement(
|
||||
uint serverGuid,
|
||||
AnimatedEntity ae,
|
||||
RemoteMotion rm,
|
||||
System.Numerics.Vector3 velocity)
|
||||
{
|
||||
// UM grace: a fresh UM is authoritative.
|
||||
double nowSec = (System.DateTime.UtcNow - System.DateTime.UnixEpoch).TotalSeconds;
|
||||
double sinceUm = nowSec - rm.LastUMTime;
|
||||
if (sinceUm < UmGraceSeconds) return;
|
||||
|
||||
uint currentMotion = ae.Sequencer!.CurrentMotion;
|
||||
uint lowByte = currentMotion & 0xFFu;
|
||||
float currentSign = MathF.Sign(ae.Sequencer.CurrentSpeedMod);
|
||||
if (currentSign == 0f) currentSign = 1f;
|
||||
|
||||
// Recognised locomotion directions:
|
||||
// 0x05 (WalkForward) — also encodes WalkBackward via negative speed
|
||||
// (ACE convention: SidestepCommand= cancel, ForwardCommand=
|
||||
// WalkForward, ForwardSpeed *= -0.65)
|
||||
// 0x07 (RunForward)
|
||||
// 0x0F (SideStepRight)
|
||||
// 0x10 (SideStepLeft)
|
||||
// Other motions (Ready, Turn, emotes, attacks) are left to UM-driven SetCycle.
|
||||
const uint LowWalkForward = 0x05u;
|
||||
const uint LowRunForward = 0x07u;
|
||||
const uint LowSideStepRight = 0x0Fu;
|
||||
const uint LowSideStepLeft = 0x10u;
|
||||
bool isForwardClass = lowByte == LowWalkForward || lowByte == LowRunForward;
|
||||
bool isSidestep = lowByte == LowSideStepRight || lowByte == LowSideStepLeft;
|
||||
if (!isForwardClass && !isSidestep) return;
|
||||
|
||||
float horizSpeed = MathF.Sqrt(velocity.X * velocity.X + velocity.Y * velocity.Y);
|
||||
|
||||
// Hysteresis: stay in current bucket unless we cross the appropriate
|
||||
// threshold. Below StopSpeed → don't refine (let UM Ready stop signal
|
||||
// handle the stop transition; we don't want UP momentary 0-velocity
|
||||
// to drop the cycle to Ready while the actor is mid-stride).
|
||||
if (horizSpeed < AcDream.Core.Physics.ServerControlledLocomotion.StopSpeed)
|
||||
return;
|
||||
|
||||
uint targetMotion;
|
||||
float speedMod;
|
||||
|
||||
if (isSidestep)
|
||||
{
|
||||
// Sidestep: motion ID stays the same (SideStepLeft / SideStepRight).
|
||||
// Retail's wire encoding for sidestep speed buckets uses the same
|
||||
// motion ID with different SidestepSpeed (slow ≈ 1.25 multiplier,
|
||||
// fast ≈ 3.0 clamp per ACE MovementData.cs:124-131). On Shift
|
||||
// toggle while a strafe key is held, retail does NOT broadcast a
|
||||
// fresh MoveToState (same wire-silence rule as the forward case),
|
||||
// so observer-side cycle refinement must come from UP-derived
|
||||
// velocity here. Preserve the sign — SideStepLeft is sometimes
|
||||
// emitted with negative speedMod by the adjust_motion path.
|
||||
//
|
||||
// Magnitude: horizSpeed / SidestepAnimSpeed maps the observed
|
||||
// speed back to a SideStepSpeed the sequencer can apply as a
|
||||
// framerate multiplier. Retail's get_state_velocity for
|
||||
// sidestep cycles is `velocity.X = SidestepAnimSpeed *
|
||||
// SideStepSpeed` (MotionInterpreter.cs:592 — constant 1.25
|
||||
// m/s). Dividing by WalkAnimSpeed (3.12) here was wrong by
|
||||
// 2.5× and made slow strafe play visibly slower than retail.
|
||||
float sideMag = horizSpeed / AcDream.Core.Physics.MotionInterpreter.SidestepAnimSpeed;
|
||||
sideMag = MathF.Min(MathF.Max(
|
||||
sideMag,
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MinSpeedMod),
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MaxSpeedMod);
|
||||
targetMotion = currentMotion;
|
||||
speedMod = sideMag * currentSign;
|
||||
}
|
||||
else if (currentSign < 0f)
|
||||
{
|
||||
// BACKWARD walk: ACE encodes WalkBackward as `WalkForward` motion
|
||||
// with NEGATIVE speedMod (MovementData.cs:115 `interpState.ForwardSpeed *= -0.65f`).
|
||||
// No "RunBackward" motion exists — Shift toggle on backward
|
||||
// changes only the magnitude of speedMod (slow back ≈ -0.65,
|
||||
// fast back ≈ -1.91 = -runRate × 0.65). Keep WalkForward motion,
|
||||
// refine magnitude, preserve negative sign.
|
||||
//
|
||||
// Without this branch (the original fix #1), backward refinement
|
||||
// computed a positive speedMod from horizSpeed and overwrote the
|
||||
// negative sign, making the legs play forward-walk while the body
|
||||
// continued moving backward (the rubber-banding the user reported).
|
||||
float backMag = horizSpeed / AcDream.Core.Physics.MotionInterpreter.WalkAnimSpeed;
|
||||
backMag = MathF.Min(MathF.Max(
|
||||
backMag,
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MinSpeedMod),
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MaxSpeedMod);
|
||||
targetMotion = AcDream.Core.Physics.MotionCommand.WalkForward;
|
||||
speedMod = -backMag;
|
||||
}
|
||||
else if (lowByte == LowRunForward)
|
||||
{
|
||||
if (horizSpeed < PlayerRunDemoteSpeed)
|
||||
{
|
||||
targetMotion = AcDream.Core.Physics.MotionCommand.WalkForward;
|
||||
speedMod = MathF.Min(MathF.Max(
|
||||
horizSpeed / AcDream.Core.Physics.MotionInterpreter.WalkAnimSpeed,
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MinSpeedMod),
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MaxSpeedMod);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetMotion = AcDream.Core.Physics.MotionCommand.RunForward;
|
||||
speedMod = MathF.Min(MathF.Max(
|
||||
horizSpeed / AcDream.Core.Physics.MotionInterpreter.RunAnimSpeed,
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MinSpeedMod),
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MaxSpeedMod);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// currently WalkForward (0x05) with positive speedMod = walking forward.
|
||||
if (horizSpeed > PlayerRunPromoteSpeed)
|
||||
{
|
||||
targetMotion = AcDream.Core.Physics.MotionCommand.RunForward;
|
||||
speedMod = MathF.Min(MathF.Max(
|
||||
horizSpeed / AcDream.Core.Physics.MotionInterpreter.RunAnimSpeed,
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MinSpeedMod),
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MaxSpeedMod);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetMotion = AcDream.Core.Physics.MotionCommand.WalkForward;
|
||||
speedMod = MathF.Min(MathF.Max(
|
||||
horizSpeed / AcDream.Core.Physics.MotionInterpreter.WalkAnimSpeed,
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MinSpeedMod),
|
||||
AcDream.Core.Physics.ServerControlledLocomotion.MaxSpeedMod);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the SetCycle if neither motion nor speedMod changed
|
||||
// meaningfully — avoids replaying transition links every UP.
|
||||
bool motionChanged = currentMotion != targetMotion
|
||||
&& (currentMotion & 0xFFu) != (targetMotion & 0xFFu);
|
||||
bool speedChanged = MathF.Abs(ae.Sequencer.CurrentSpeedMod - speedMod) > 0.05f;
|
||||
if (!motionChanged && !speedChanged)
|
||||
return;
|
||||
|
||||
if (System.Environment.GetEnvironmentVariable("ACDREAM_REMOTE_VEL_DIAG") == "1")
|
||||
{
|
||||
System.Console.WriteLine(
|
||||
$"[UPCYCLE_PLAYER] guid={serverGuid:X8} "
|
||||
+ $"|v|={horizSpeed:F2} cur=0x{currentMotion:X8} "
|
||||
+ $"-> motion=0x{targetMotion:X8} speedMod={speedMod:F2} "
|
||||
+ $"sinceUM={sinceUm:F2}s "
|
||||
+ $"motionChg={motionChanged} speedChg={speedChanged}");
|
||||
}
|
||||
|
||||
uint style = ae.Sequencer.CurrentStyle != 0
|
||||
? ae.Sequencer.CurrentStyle
|
||||
: 0x8000003Du;
|
||||
ae.Sequencer.SetCycle(style, targetMotion, speedMod);
|
||||
}
|
||||
|
||||
private void OnLivePositionUpdated(AcDream.Core.Net.WorldSession.EntityPositionUpdate update)
|
||||
{
|
||||
// Phase A.1 / #135: track the PLAYER's last server-known landblock so the
|
||||
|
|
@ -5634,24 +5420,14 @@ public sealed class GameWindow : IDisposable
|
|||
isMovingTo: false,
|
||||
currentBodyPosition: rmState.Body.Position);
|
||||
}
|
||||
// #39 fix-3 (2026-05-06): velocity-fallback cycle refinement
|
||||
// for player remotes. Wire-level evidence (`launch-39-diag2.log`):
|
||||
// when retail's actor toggles Shift while a direction key
|
||||
// is held, retail's outbound MoveToState logic does NOT
|
||||
// emit a fresh packet (only Ready ↔ Run UMs visible in
|
||||
// `[FWD_WIRE]`, despite a clear walk-pace ↔ run-pace
|
||||
// velocity transition in `[VEL_DIAG]`). ACE has nothing
|
||||
// to broadcast → no UM arrives at the observer → cycle
|
||||
// sticks at whatever the last UM set. Compute the
|
||||
// synth-velocity here in the player-remote path AND
|
||||
// call into ApplyServerControlledVelocityCycle, which
|
||||
// routes through the direction-preserving + UM-grace
|
||||
// ApplyPlayerLocomotionRefinement helper (added in
|
||||
// commit 8fa04af).
|
||||
//
|
||||
// The legacy non-player block below (3759+) covers NPCs
|
||||
// and is gated `!IsPlayerGuid`; this block fills the
|
||||
// matching gap for players.
|
||||
// Track the UP-derived synth velocity for diagnostics
|
||||
// ([VEL_DIAG] pace comparison). L.2g S5 (2026-07-02): the
|
||||
// #39-era cycle-refinement call that used to live here is
|
||||
// DELETED — retail never adapts a remote's animation from
|
||||
// observed pace (deviation map DEV-2; premise refuted at
|
||||
// decomp + ACE source + the S0 live capture, which shows
|
||||
// explicit 0x0005↔0x0007 UMs on every Shift toggle).
|
||||
// Player-remote cycles are UM-driven only.
|
||||
if (rmState.PrevServerPosTime > 0.0)
|
||||
{
|
||||
double nowSecVel = rmState.LastServerPosTime;
|
||||
|
|
@ -5661,21 +5437,6 @@ public sealed class GameWindow : IDisposable
|
|||
var synthVel = (worldPos - rmState.PrevServerPos) / (float)dtPos;
|
||||
rmState.ServerVelocity = synthVel;
|
||||
rmState.HasServerVelocity = true;
|
||||
|
||||
if (_animatedEntities.TryGetValue(entity.Id, out var aeForVel)
|
||||
&& aeForVel.Sequencer is not null)
|
||||
{
|
||||
if (System.Environment.GetEnvironmentVariable("ACDREAM_REMOTE_VEL_DIAG") == "1")
|
||||
{
|
||||
System.Console.WriteLine(
|
||||
$"[UPCYCLE_SRC] guid={update.Guid:X8} src=synth-player");
|
||||
}
|
||||
ApplyServerControlledVelocityCycle(
|
||||
update.Guid,
|
||||
aeForVel,
|
||||
rmState,
|
||||
synthVel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5793,17 +5554,16 @@ public sealed class GameWindow : IDisposable
|
|||
if (rmState.HasServerVelocity
|
||||
&& _animatedEntities.TryGetValue(entity.Id, out var aeForVelocity))
|
||||
{
|
||||
// #39 (2026-05-06): un-gated for player remotes — the
|
||||
// function itself routes player remotes into the dedicated
|
||||
// ApplyPlayerLocomotionRefinement path (forward-direction
|
||||
// speed bucket only, with UM grace + hysteresis). Non-player
|
||||
// remotes use the existing PlanFromVelocity path.
|
||||
// NPC/monster remotes: PlanFromVelocity cycle selection from
|
||||
// UP-derived velocity (ACE broadcasts NPC motion patterns the
|
||||
// UM stream alone doesn't cover). Player remotes return early
|
||||
// inside — their cycles are UM-driven only per retail (L.2g
|
||||
// S5; DEV-2 deleted). Unification of NPCs onto the
|
||||
// CMotionInterp funnel is S6.
|
||||
//
|
||||
// D2 (Commit A 2026-05-03): tag whether the velocity feeding
|
||||
// ApplyServerControlledVelocityCycle is wire-explicit (rare for
|
||||
// player remotes — ACE almost never sets HasVelocity on player
|
||||
// UPs) or synthesized from position deltas (the common case).
|
||||
// Pairs with the [UPCYCLE]/[UPCYCLE_PLAYER] line printed inside.
|
||||
// ApplyServerControlledVelocityCycle is wire-explicit or
|
||||
// synthesized from position deltas (the common case).
|
||||
if (System.Environment.GetEnvironmentVariable("ACDREAM_REMOTE_VEL_DIAG") == "1")
|
||||
{
|
||||
string velSrc = update.Velocity is null ? "synth" : "wire";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue