From 870a8a257d6788f56d5c540022d9e5c381a11439 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sat, 1 Aug 2026 20:49:25 -0500 Subject: [PATCH] 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 --- src/RioJoy.Core/Calibration/AxisCalibrator.cs | 21 +++++++++++++-- .../Calibration/AxisCalibratorTests.cs | 27 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/RioJoy.Core/Calibration/AxisCalibrator.cs b/src/RioJoy.Core/Calibration/AxisCalibrator.cs index d05a708..be5ca99 100644 --- a/src/RioJoy.Core/Calibration/AxisCalibrator.cs +++ b/src/RioJoy.Core/Calibration/AxisCalibrator.cs @@ -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; } diff --git a/tests/RioJoy.Core.Tests/Calibration/AxisCalibratorTests.cs b/tests/RioJoy.Core.Tests/Calibration/AxisCalibratorTests.cs index 7496c54..6189fbe 100644 --- a/tests/RioJoy.Core.Tests/Calibration/AxisCalibratorTests.cs +++ b/tests/RioJoy.Core.Tests/Calibration/AxisCalibratorTests.cs @@ -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); + } }