calibrator: exact center in must be exact center out
The legacy joystick math left the output unchanged when the raw sample was exactly 0 - a case a jittering pot never produces, so on real hardware the hold was invisible. vRIO's pad deadzone produces it constantly: every release of the bench gamepad shapes the residual to exactly 0.0 and holds it there, one sample per 55 ms poll. The calibrator held the last in-motion output against that stream indefinitely, so the virtual pad froze off-center at whatever the stick commanded the instant before release. Flown, that read as a Descent 3 pod drifting on yaw and pitch with the stick centered, stopping only when the release was slow enough to land an intermediate sample inside the +/-5 band. It survived a full day of suspects - the game's deadzone, its axis map, its focus handling, com0com, the serial protocol - because every layer below the calibrator was correct: the freeze was visible in joy.cpl itself, and the game's own control trace showed heading latched at -0.38 while the wire carried perfect zeros. Same divergence from the legacy port, same reasoning, as the throttle detent fix above it: zero means centered, not "no information". Both axes; regression tests pin the fast-release case and center stability across repeated zero polls. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -200,7 +200,19 @@ public sealed class AxisCalibrator
|
||||
// 16838 (not 16383) and a +2 nudge are deliberate legacy anti-snap tweaks.
|
||||
_joystickXLast = lJx > 0 ? 16838 - ((lJx + 2) * sRightRate) : AxisOutputs.Center;
|
||||
}
|
||||
// lJx == 0 leaves _joystickXLast unchanged (legacy behavior).
|
||||
else
|
||||
{
|
||||
// Deliberate divergence from the legacy port, same reasoning as the
|
||||
// throttle detent above: the legacy held the previous output on a raw
|
||||
// of exactly 0. A real pot jitters and never rests at exact 0, so the
|
||||
// hold was invisible on hardware - but vRIO's pad deadzone emits
|
||||
// sustained exact zeros on release, and the hold latched the last
|
||||
// in-motion output indefinitely: the ship kept turning at whatever
|
||||
// rate the stick commanded the instant before release (bench
|
||||
// 2026-08-01, Descent 3 drifting on yaw/pitch with the stick
|
||||
// centered). Exact center in must be exact center out.
|
||||
_joystickXLast = AxisOutputs.Center;
|
||||
}
|
||||
|
||||
return _config.InvertX ? AxisOutputs.Max - _joystickXLast : _joystickXLast;
|
||||
}
|
||||
@@ -223,7 +235,12 @@ public sealed class AxisCalibrator
|
||||
lJy -= DeadzoneJoystick;
|
||||
_joystickYLast = lJy > 0 ? AxisOutputs.Center - (lJy * sDownRate) : AxisOutputs.Center;
|
||||
}
|
||||
// lJy == 0 leaves _joystickYLast unchanged (legacy behavior).
|
||||
else
|
||||
{
|
||||
// Exact 0 centers rather than holding the previous output - see the
|
||||
// matching branch in JoystickX for the full story.
|
||||
_joystickYLast = AxisOutputs.Center;
|
||||
}
|
||||
|
||||
return _config.InvertY ? AxisOutputs.Max - _joystickYLast : _joystickYLast;
|
||||
}
|
||||
|
||||
@@ -163,4 +163,31 @@ public class AxisCalibratorTests
|
||||
cal.Update(Report(throttle: -400)); // power-on with lever pushed: becomes the start
|
||||
Assert.Equal(0, cal.Update(Report(throttle: -100)).Z); // above max → re-based, rest
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Joystick_ReleaseToExactZero_Centers_NotHeld()
|
||||
{
|
||||
// Regression: the legacy left the output unchanged on a raw of exactly 0,
|
||||
// which a jittering pot never produces - but vRIO's pad deadzone does,
|
||||
// sustained, on every release. The held output latched the last in-motion
|
||||
// value and the ship kept turning with the stick centered (bench
|
||||
// 2026-08-01). A fast release goes straight from deflected to exact 0
|
||||
// with no intermediate sample; both axes must land on center.
|
||||
var cal = new AxisCalibrator();
|
||||
cal.Update(Report(x: -3000, y: 2000)); // hard over
|
||||
AxisOutputs o = cal.Update(Report(x: 0, y: 0)); // released, deadzoned to exact 0
|
||||
Assert.Equal(AxisOutputs.Center, o.X);
|
||||
Assert.Equal(AxisOutputs.Center, o.Y);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Joystick_ExactZero_StaysCentered_AcrossRepeatedPolls()
|
||||
{
|
||||
// The latch fed forward: once held off-center, every subsequent exact-0
|
||||
// poll (one per 55 ms, forever) kept it there. Center must be stable.
|
||||
var cal = new AxisCalibrator();
|
||||
cal.Update(Report(x: 5000));
|
||||
for (int i = 0; i < 5; i++)
|
||||
Assert.Equal(AxisOutputs.Center, cal.Update(Report(x: 0)).X);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user