feat(motion): L.3 M2 — queue-only chase for grounded player remotes
Wires the M1 InterpolationManager into the per-tick + UP-receipt paths in GameWindow for player remote entities. Visual-verified against a retail-controlled remote: smooth body chase, no per-UP rubber-band, no staircase on slopes. OnLivePositionUpdated: - Gate changed from `ACDREAM_INTERP_MANAGER == "1"` to `IsPlayerGuid(update.Guid)`. NPCs continue through the legacy synth-velocity branch (ServerVelocity / ServerMoveTo) below — their motion model is correct as-is. - Within-bubble enqueue passes `currentBodyPosition` so the M1 far- branch detection (>100 m from body) can pre-arm an immediate blip. - Three branches (airborne no-op, near-enqueue, far-snap) now sync `entity.Position = rmState.Body.Position` before returning. This overrides the unconditional `entity.Position = worldPos` snap at the top of the function. Without this sync the entity teleports forward to server truth on UP receipt and TickAnimations yanks it back to the queue-driven body next frame — visible 0.5–1 m rubber- band per UP. TickAnimations: - Gate changed from `ACDREAM_INTERP_MANAGER == "1"` to `IsPlayerGuid(serverGuid) && !rm.Airborne`. Airborne player remotes fall through to the legacy path so K-fix15 landing + gravity sweep still fire on the jump arc. - Step 2 (per-frame translation) replaced. Was `rm.Position.ComputeOffset(...)` (mixed queue catch-up + animation root motion); now direct `rm.Interp.AdjustOffset(...)` (queue-only, no anim contribution). M3 will layer anim root motion on top so legs match body pace; for M2 the body chases server position smoothly without any anim-driven translation. - Step 4b (ResolveWithTransition collision sweep) REMOVED for player remotes. Server already collision-resolved the broadcast position; running the sweep on tiny per-frame queue catch-up deltas amplified micro-bounces into the ISSUES.md #40 staircase + flat-ground blips. - Step 5 (LastServerZ landing fallback) REMOVED — unreachable in the `!rm.Airborne` branch. Per retail spec (docs/research/2026-05-04-l3-port/01-per-tick.md + 04-interp-manager.md): m_velocityVector stays 0 for grounded remotes, apply_current_movement is local-player-only, and per-tick translation comes entirely from InterpolationManager queue catch-up. Behavior for player remotes: | Scenario | Path | Translation source | |-----------------------|--------|------------------------------| | Grounded near (≤96m) | M2 | Queue catch-up (2× max-speed)| | Grounded far (>96m) | M2 | Hard-snap to worldPos | | Far enqueue (>100m) | M2 | Pre-armed blip-to-tail | | Airborne (mid-jump) | Legacy | Gravity arc + sweep | | Landing | M2 | Hard-snap, queue cleared | NPCs: legacy path unchanged (synth velocity, ServerMoveTo, etc.). Closes the regression observed in 9b0f4f2 ("modern, not retail-faithful") and the L.3 attempts on 91bf1e0 /e94e791. Replaces the env-var path (ACDREAM_INTERP_MANAGER=1) which was marked DO-NOT-ENABLE in ISSUES.md #40 — the env-var no longer toggles anything for player remotes; this IS the path now. Build green, dotnet test green (8 pre-existing failures unchanged on this baseline; verified via stash ona3f53c2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
de129bc164
commit
40d88b92ed
1 changed files with 81 additions and 226 deletions
|
|
@ -3436,11 +3436,19 @@ public sealed class GameWindow : IDisposable
|
|||
rmState.Body.Orientation = rot;
|
||||
}
|
||||
|
||||
// L.3.1 Task 4: env-var gated retail-faithful MoveOrTeleport routing.
|
||||
// Mirrors CPhysicsObj::MoveOrTeleport (acclient @ 0x00516330).
|
||||
// Enabled only when ACDREAM_INTERP_MANAGER=1 to keep default behavior
|
||||
// identical to before this commit. Legacy hard-snap path remains below.
|
||||
if (System.Environment.GetEnvironmentVariable("ACDREAM_INTERP_MANAGER") == "1")
|
||||
// L.3 M2 (2026-05-05): retail-faithful MoveOrTeleport routing for
|
||||
// player remotes. Mirrors CPhysicsObj::MoveOrTeleport
|
||||
// (acclient @ 0x00516330) — airborne no-op, far-snap, near
|
||||
// InterpolateTo. Gated on IsPlayerGuid so NPCs continue through
|
||||
// the legacy synth-velocity branch below; their motion comes
|
||||
// from ServerVelocity / ServerMoveTo which the legacy path
|
||||
// already handles correctly.
|
||||
//
|
||||
// Was previously gated on ACDREAM_INTERP_MANAGER=1; the env-var
|
||||
// path's per-tick TickAnimations counterpart is regressed
|
||||
// (issue #40). M2 keeps the OnLivePositionUpdated half (which
|
||||
// is correct) and rewrites the per-tick half — see TickAnimations.
|
||||
if (IsPlayerGuid(update.Guid))
|
||||
{
|
||||
// Orientation always snaps on receipt — InterpolationManager walks
|
||||
// position only; heading would otherwise lag the queue.
|
||||
|
|
@ -3499,7 +3507,15 @@ public sealed class GameWindow : IDisposable
|
|||
// integrating gravity via per-frame UpdatePhysicsInternal. Server is
|
||||
// authoritative for the arc; we don't predict it locally.
|
||||
if (!update.IsGrounded)
|
||||
{
|
||||
// Undo the unconditional entity hard-snap at the top of the
|
||||
// function (entity.Position = worldPos): the body is mid-arc
|
||||
// and TickAnimations will write entity = body next frame
|
||||
// anyway. Setting entity = body now prevents a 1-frame
|
||||
// teleport-to-server-then-yank-back rubber-band.
|
||||
entity.Position = rmState.Body.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
// ── LANDING TRANSITION ────────────────────────────────────────
|
||||
// First IsGrounded=true UP after rmState.Airborne signals landed.
|
||||
|
|
@ -3547,12 +3563,26 @@ public sealed class GameWindow : IDisposable
|
|||
else
|
||||
{
|
||||
// Within view bubble: enqueue waypoint for adjust_offset to walk to.
|
||||
// PositionManager (called per-frame in TickAnimations) handles the
|
||||
// actual body advancement — mix of animation root motion + queue
|
||||
// correction.
|
||||
// The per-frame TickAnimations player-remote path drives the
|
||||
// actual body advancement via InterpolationManager.AdjustOffset.
|
||||
// Pass body's current position so the InterpolationManager can
|
||||
// detect a far-distance enqueue (>100 m from body) and pre-arm
|
||||
// an immediate blip — avoids body drifting visibly toward a
|
||||
// far waypoint instead of teleporting to it.
|
||||
float headingFromQuat = ExtractYawFromQuaternion(rot);
|
||||
rmState.Interp.Enqueue(worldPos, headingFromQuat, isMovingTo: false);
|
||||
rmState.Interp.Enqueue(
|
||||
worldPos,
|
||||
headingFromQuat,
|
||||
isMovingTo: false,
|
||||
currentBodyPosition: rmState.Body.Position);
|
||||
}
|
||||
// Sync the visible entity to the body — overrides the unconditional
|
||||
// entity.Position = worldPos snap at the top of this function.
|
||||
// For the far-snap branch this is a no-op (body == worldPos); for
|
||||
// the near-enqueue branch this prevents a 1-frame teleport-then-
|
||||
// yank-back rubber-band as TickAnimations chases worldPos via the
|
||||
// queue.
|
||||
entity.Position = rmState.Body.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -6039,76 +6069,28 @@ public sealed class GameWindow : IDisposable
|
|||
&& serverGuid != _playerServerGuid
|
||||
&& _remoteDeadReckon.TryGetValue(serverGuid, out var rm))
|
||||
{
|
||||
if (System.Environment.GetEnvironmentVariable("ACDREAM_INTERP_MANAGER") == "1")
|
||||
if (IsPlayerGuid(serverGuid) && !rm.Airborne)
|
||||
{
|
||||
// ⚠️ REGRESSED 2026-05-03 — DO NOT ENABLE — see docs/ISSUES.md #40 ⚠️
|
||||
// ── L.3 M2 (2026-05-05): queue-only chase for grounded player remotes ──
|
||||
//
|
||||
// Introduced by e94e791 (L.3.1+L.3.2 Task 3) intending to
|
||||
// mirror retail CPhysicsObj::MoveOrTeleport (network-packet
|
||||
// entry point — minimal work). Wrong retail function for the
|
||||
// per-frame tick — the actual per-frame chain is retail's
|
||||
// update_object (FUN_00515020), which the LEGACY path below
|
||||
// correctly mirrors (apply_current_movement →
|
||||
// UpdatePhysicsInternal → ResolveWithTransition collision
|
||||
// sweep). This env-var path strips the collision sweep AND
|
||||
// clears body.Velocity, leaving only PositionManager queue
|
||||
// catch-up — which stair-steps with the 1 Hz UP cadence on
|
||||
// slopes and produces visible position blips on flat ground.
|
||||
// Per retail spec (docs/research/2026-05-04-l3-port/01-per-tick.md +
|
||||
// 04-interp-manager.md):
|
||||
// - For a grounded REMOTE player, m_velocityVector stays at 0.
|
||||
// - apply_current_movement is NEVER called per tick on remotes
|
||||
// (it's the local-player-only velocity feed).
|
||||
// - UpdatePhysicsInternal's translation step is gated on
|
||||
// velocity² > 0, so it's a no-op when body.Velocity = 0.
|
||||
// - ResolveWithTransition is NOT called — the server already
|
||||
// collision-resolved the broadcast position.
|
||||
// - Per-tick body translation comes ENTIRELY from
|
||||
// InterpolationManager::adjust_offset's queue catch-up.
|
||||
// When the queue is empty (head reached, between UPs), the
|
||||
// body stays put. M3 will add animation root motion to fill
|
||||
// the gap so legs match body pace; for M2 the body chases
|
||||
// the server position without anim contribution.
|
||||
//
|
||||
// Commit B (039149a, 2026-05-03) ported ResolveWithTransition
|
||||
// here but symptom persists because body.Velocity=0 means
|
||||
// pre/postIntegrate sweep input is just the queue catch-up,
|
||||
// which itself snaps in steps. Fix requires re-integrating
|
||||
// PositionManager as ADDITIVE adjust_offset on top of the
|
||||
// legacy chain — separate L.3 follow-up phase.
|
||||
//
|
||||
// Until that lands, stay on the legacy path (env-var unset).
|
||||
// ── NEW PATH: retail-faithful per-frame remote tick ──
|
||||
// (L.3.1+L.3.2 Task 3/follow-up — ACDREAM_INTERP_MANAGER=1 gates this path)
|
||||
//
|
||||
// Per retail's CPhysicsObj::UpdateObjectInternal (0x005156b0)
|
||||
// → UpdatePositionInternal (0x00512c30) → CSequence::update
|
||||
// chain (decomp investigation 2026-05-03):
|
||||
//
|
||||
// For a REMOTE entity (not local player), per physics tick
|
||||
// the world-position advance is the sum of:
|
||||
// A) animation root motion accumulated by
|
||||
// update_internal (Frame::combine of crossed
|
||||
// per-keyframe pos_frames deltas) OR replaced by
|
||||
// InterpolationManager::adjust_offset's catch-up
|
||||
// when the body is far from the queue head.
|
||||
// B) body.Velocity × dt + 0.5 × accel × dt²
|
||||
// (UpdatePhysicsInternal). For remotes, retail does
|
||||
// NOT call apply_current_movement per tick — body.
|
||||
// Velocity stays at whatever the last
|
||||
// InterpolationManager type-3 ("set velocity") node
|
||||
// set it to (typically zero unless the server is
|
||||
// explicitly pushing velocity via VectorUpdate).
|
||||
//
|
||||
// So for normal grounded run/walk/strafe with no server-
|
||||
// pushed velocity, ALL per-tick translation comes from (A).
|
||||
//
|
||||
// Acdream port mapping:
|
||||
// - We don't extract per-keyframe pos_frames from the .anm
|
||||
// assets. Our AnimationSequencer.CurrentVelocity is the
|
||||
// synthesized equivalent (RunAnimSpeed × ForwardSpeed)
|
||||
// which averages to the same effective body translation.
|
||||
// - Pass it as seqVel to ComputeOffset so the
|
||||
// animation-root-motion path drives body translation.
|
||||
// - DO NOT call apply_current_movement per tick — that
|
||||
// would set body.Velocity to RunAnimSpeed × ForwardSpeed,
|
||||
// and UpdatePhysicsInternal would then add ANOTHER
|
||||
// 11.7 m/s × dt on top of the seqVel motion already
|
||||
// applied by ComputeOffset, producing 2× server pace
|
||||
// (the user-reported "way too fast" + 1-Hz blip from
|
||||
// the catch-up walking back the overshoot).
|
||||
// - body.Velocity stays at 0 for grounded remotes; non-
|
||||
// zero only when OnLiveVectorUpdated set it (jump
|
||||
// start) — UpdatePhysicsInternal then integrates
|
||||
// gravity for the airborne arc.
|
||||
|
||||
System.Numerics.Vector3 seqVel = ae.Sequencer?.CurrentVelocity
|
||||
?? System.Numerics.Vector3.Zero;
|
||||
// Airborne player remotes (rm.Airborne) and NPCs fall through to
|
||||
// the legacy path below — unchanged from main per the M2 plan.
|
||||
System.Numerics.Vector3 seqOmega = ae.Sequencer?.CurrentOmega
|
||||
?? System.Numerics.Vector3.Zero;
|
||||
|
||||
|
|
@ -6133,26 +6115,21 @@ public sealed class GameWindow : IDisposable
|
|||
rm.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Active;
|
||||
}
|
||||
|
||||
// Step 2: per-frame body translation. ComputeOffset returns
|
||||
// either the queue catch-up (when active) or the animation
|
||||
// root motion (seqVel × dt rotated to world). REPLACE
|
||||
// semantics — retail's PositionManager::adjust_offset
|
||||
// overwrites the offset frame with the catch-up direction,
|
||||
// not adding to it.
|
||||
//
|
||||
// 2026-05-03 (Commit B fix for staircase regression): capture
|
||||
// the pre-translation position so the collision sweep below
|
||||
// (Step 4b) can resolve the full per-tick movement through
|
||||
// BSP + terrain.
|
||||
var preIntegratePos = rm.Body.Position;
|
||||
// Step 2 (M2): queue-only translation. Direct call to
|
||||
// InterpolationManager.AdjustOffset — no PositionManager
|
||||
// mixing, no animation root motion. The InterpolationManager
|
||||
// returns:
|
||||
// - Vector3.Zero when the queue is empty OR the head is
|
||||
// within DesiredDistance (0.05 m) — body stays still.
|
||||
// - Direction × min(catchUpSpeed × dt, dist) — body chases
|
||||
// the head waypoint at up to 2× motion-table max speed.
|
||||
// - tail − body when fail_count > 3 (stall blip; queue
|
||||
// cleared as a side effect).
|
||||
float maxSpeed = rm.Motion.GetMaxSpeed();
|
||||
System.Numerics.Vector3 offset = rm.Position.ComputeOffset(
|
||||
System.Numerics.Vector3 offset = rm.Interp.AdjustOffset(
|
||||
dt: (double)dt,
|
||||
currentBodyPosition: rm.Body.Position,
|
||||
seqVel: seqVel,
|
||||
ori: rm.Body.Orientation,
|
||||
interp: rm.Interp,
|
||||
maxSpeed: maxSpeed);
|
||||
maxSpeedFromMinterp: maxSpeed);
|
||||
rm.Body.Position += offset;
|
||||
|
||||
// Step 2.5: angular velocity → body orientation. Prefer
|
||||
|
|
@ -6209,140 +6186,18 @@ public sealed class GameWindow : IDisposable
|
|||
// Step 4: physics integration (Euler: pos += vel*dt + 0.5*accel*dt²).
|
||||
rm.Body.UpdatePhysicsInternal(dt);
|
||||
|
||||
// Step 4b (Commit B fix 2026-05-03): collision sweep — port of
|
||||
// retail update_object's FUN_005148A0 Transition::FindTransitionalPosition.
|
||||
// This was MISSING in the env-var path introduced by e94e791
|
||||
// (L.3.1+L.3.2 Task 3). The legacy (env-var off) path at the
|
||||
// bottom of this function has it (line ~6483 "Step 4: collision
|
||||
// sweep"); we just need the same call here.
|
||||
// Step 4b INTENTIONALLY OMITTED in M2:
|
||||
// ResolveWithTransition is NOT called — the server has
|
||||
// already collision-resolved the broadcast position, and
|
||||
// running our sweep on tiny per-frame queue catch-up deltas
|
||||
// amplifies micro-bounces into visible position blips
|
||||
// (issue #40 staircase + flat-ground blips). Per retail
|
||||
// spec the per-tick body advance for a remote is purely
|
||||
// the queue catch-up; collision is the sender's problem.
|
||||
//
|
||||
// Without this:
|
||||
// - Body Z drifts on slopes (visible "staircase" — horizontal
|
||||
// Euler motion up a slope sinks into rising ground until
|
||||
// the next UP pops it up).
|
||||
// - Body slides through walls / objects between UPs.
|
||||
// - Step-up / step-down doesn't engage on ledges.
|
||||
// - Edge-slide doesn't engage on cliff edges.
|
||||
//
|
||||
// The env-var path was originally designed to mirror retail
|
||||
// CPhysicsObj::MoveOrTeleport (acclient @ 0x00516330) — a network
|
||||
// packet handler entry point that does minimal work. But
|
||||
// TickAnimations is the per-frame physics tick (mirrors retail
|
||||
// FUN_00515020 update_object), which DOES include the collision
|
||||
// sweep. Adding the sweep here makes the env-var path retail-
|
||||
// faithful for the per-frame tick (matching the legacy path,
|
||||
// which had it).
|
||||
var postIntegratePos = rm.Body.Position;
|
||||
if (rm.CellId != 0 && _physicsEngine.LandblockCount > 0)
|
||||
{
|
||||
// Sphere dims match local-player + legacy-path defaults
|
||||
// (~0.48m radius, ~1.2m height humanoid). Step-up/down 0.4m
|
||||
// matches L.2.3a retail human-scale. EdgeSlide is the retail
|
||||
// default mover-flags state.
|
||||
var resolveResult = _physicsEngine.ResolveWithTransition(
|
||||
preIntegratePos, postIntegratePos, rm.CellId,
|
||||
sphereRadius: 0.48f,
|
||||
sphereHeight: 1.2f,
|
||||
stepUpHeight: 0.4f,
|
||||
stepDownHeight: 0.4f,
|
||||
// Airborne remotes must NOT pre-seed the ContactPlane —
|
||||
// mirrors K-fix9 in the legacy path; otherwise
|
||||
// AdjustOffset's snap-to-plane branch zeroes the +Z
|
||||
// offset every step on a jump arc.
|
||||
isOnGround: !rm.Airborne,
|
||||
body: rm.Body,
|
||||
moverFlags: AcDream.Core.Physics.ObjectInfoState.EdgeSlide);
|
||||
|
||||
rm.Body.Position = resolveResult.Position;
|
||||
if (resolveResult.CellId != 0)
|
||||
rm.CellId = resolveResult.CellId;
|
||||
|
||||
// Post-resolve landing detection — mirrors K-fix15 in the
|
||||
// legacy path. When the resolver says we're on ground AND
|
||||
// velocity is no longer pointing up, transition back to
|
||||
// grounded. Without this, gravity keeps building negative Z
|
||||
// velocity until the sphere-sweep clamps each frame, but
|
||||
// Airborne stays true forever.
|
||||
if (rm.Airborne
|
||||
&& resolveResult.IsOnGround
|
||||
&& rm.Body.Velocity.Z <= 0f)
|
||||
{
|
||||
rm.Airborne = false;
|
||||
rm.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Contact
|
||||
| AcDream.Core.Physics.TransientStateFlags.OnWalkable;
|
||||
rm.Body.State &= ~AcDream.Core.Physics.PhysicsStateFlags.Gravity;
|
||||
rm.Body.Velocity = new System.Numerics.Vector3(
|
||||
rm.Body.Velocity.X, rm.Body.Velocity.Y, 0f);
|
||||
rm.Motion.HitGround();
|
||||
|
||||
// Reset sequencer cycle from Falling back to whatever
|
||||
// InterpretedState says. Mirrors K-fix17 in the legacy
|
||||
// path.
|
||||
if (ae.Sequencer is not null)
|
||||
{
|
||||
uint landStyle = ae.Sequencer.CurrentStyle != 0
|
||||
? ae.Sequencer.CurrentStyle
|
||||
: 0x8000003Du;
|
||||
uint landingCmd = rm.Motion.InterpretedState.ForwardCommand;
|
||||
if (landingCmd == 0)
|
||||
landingCmd = AcDream.Core.Physics.MotionCommand.Ready;
|
||||
float landingSpeed = rm.Motion.InterpretedState.ForwardSpeed;
|
||||
if (landingSpeed <= 0f) landingSpeed = 1f;
|
||||
ae.Sequencer.SetCycle(landStyle, landingCmd, landingSpeed);
|
||||
}
|
||||
|
||||
if (Environment.GetEnvironmentVariable("ACDREAM_DUMP_MOTION") == "1")
|
||||
Console.WriteLine($"VU.land guid=0x{serverGuid:X8} Z={rm.Body.Position.Z:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: landing fallback. The retail-faithful path leaves
|
||||
// the landing transition to OnLivePositionUpdated when ACE
|
||||
// sends IsGrounded=true. In practice ACE doesn't always
|
||||
// broadcast that flag promptly — the body keeps falling
|
||||
// under gravity and visibly disappears into the ground until
|
||||
// the next non-stop UP arrives (e.g. when the player turns).
|
||||
// The remote's most recent server-reported Z is an
|
||||
// authoritative ground floor: if our predicted body has
|
||||
// sunk below it by more than half a meter, snap up to it
|
||||
// and clear airborne, mirroring the OnLivePositionUpdated
|
||||
// landing-transition branch. Threshold matches retail's
|
||||
// MIN_DISTANCE_TO_REACH_POSITION-style tolerance.
|
||||
if (rm.Airborne
|
||||
&& !float.IsNaN(rm.LastServerZ)
|
||||
&& rm.Body.Position.Z < rm.LastServerZ - 0.5f)
|
||||
{
|
||||
rm.Airborne = false;
|
||||
rm.Body.Velocity = System.Numerics.Vector3.Zero;
|
||||
rm.Body.State &= ~AcDream.Core.Physics.PhysicsStateFlags.Gravity;
|
||||
rm.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Contact
|
||||
| AcDream.Core.Physics.TransientStateFlags.OnWalkable;
|
||||
rm.Interp.Clear();
|
||||
rm.Body.Position = new System.Numerics.Vector3(
|
||||
rm.Body.Position.X, rm.Body.Position.Y, rm.LastServerZ);
|
||||
|
||||
// Swap the sequencer out of Falling — without this the
|
||||
// legs stay folded in the airborne pose forever even
|
||||
// though the body is now planted on the ground. Mirrors
|
||||
// the legacy K-fix17 path at the bottom of TickAnimations
|
||||
// (line ~6284): pick the cycle from the last-known
|
||||
// InterpretedState.ForwardCommand, falling back to Ready
|
||||
// when nothing is held. The next UpdateMotion the server
|
||||
// sends will refine if the player was strafing/turning
|
||||
// mid-jump; this just gets them out of Falling now.
|
||||
if (ae.Sequencer is not null)
|
||||
{
|
||||
uint style = ae.Sequencer.CurrentStyle != 0
|
||||
? ae.Sequencer.CurrentStyle
|
||||
: 0x8000003Du;
|
||||
uint landingCmd = rm.Motion.InterpretedState.ForwardCommand;
|
||||
if (landingCmd == 0)
|
||||
landingCmd = AcDream.Core.Physics.MotionCommand.Ready;
|
||||
float landingSpeed = rm.Motion.InterpretedState.ForwardSpeed;
|
||||
if (landingSpeed <= 0f) landingSpeed = 1f;
|
||||
ae.Sequencer.SetCycle(style, landingCmd, landingSpeed);
|
||||
}
|
||||
}
|
||||
// Step 5 (landing fallback) is unreachable in this branch —
|
||||
// we're gated on !rm.Airborne. Airborne player remotes fall
|
||||
// through to the legacy path below where K-fix15 still fires.
|
||||
|
||||
// Step 6: speed-overshoot diagnostic (ACDREAM_REMOTE_VEL_DIAG=1).
|
||||
// Track the maximum sequencer velocity magnitude seen since
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue