diff --git a/src/VRio.App/MainForm.cs b/src/VRio.App/MainForm.cs index 5fd415e..9da31a0 100644 --- a/src/VRio.App/MainForm.cs +++ b/src/VRio.App/MainForm.cs @@ -190,8 +190,8 @@ internal sealed class MainForm : Form if (!_kbInput.Checked) _router.ReleaseAllKeys(); }; - _invertY.CheckedChanged += (_, _) => _router.InvertJoystickY = _invertY.Checked; - _router.InvertJoystickY = _invertY.Checked; // the field initializer raises no event + _invertY.CheckedChanged += (_, _) => _device.InvertJoystickY = _invertY.Checked; + _device.InvertJoystickY = _invertY.Checked; // the field initializer raises no event _kbLights.CheckedChanged += (_, _) => { _lampMirror.Enabled = _kbLights.Checked; diff --git a/src/VRio.Core/Device/VRioDevice.cs b/src/VRio.Core/Device/VRioDevice.cs index b2c4f1c..f169a19 100644 --- a/src/VRio.Core/Device/VRioDevice.cs +++ b/src/VRio.Core/Device/VRioDevice.cs @@ -57,6 +57,13 @@ public sealed class VRioDevice /// Firmware version reported by VersionReply (real boards run 4.2). public byte VersionMinor { get; set; } = 2; + /// + /// Negate joystick Y in AnalogReply. Only the host sees the flip — local + /// state (, the panel's dot and readout) keeps the + /// physical stick direction. + /// + public bool InvertJoystickY { get; set; } + /// /// When true, retry exhaustion leaves the analog reply path wedged (the /// v4.2 latch-leak bug) until a host ResetRequest clears it. @@ -274,6 +281,8 @@ public sealed class VRioDevice y = _axes[(int)RioAxis.JoystickY]; x = _axes[(int)RioAxis.JoystickX]; } + if (InvertJoystickY) // clamp: -Min (8192) is one past the 14-bit Max + y = (short)Math.Min(AnalogCodec.Max, -y); Send(PacketBuilder.AnalogReply(t, l, r, y, x)); break; diff --git a/src/VRio.Core/Input/InputRouter.cs b/src/VRio.Core/Input/InputRouter.cs index f00221b..fac9776 100644 --- a/src/VRio.Core/Input/InputRouter.cs +++ b/src/VRio.Core/Input/InputRouter.cs @@ -57,13 +57,6 @@ public sealed class InputRouter /// A routed address went down (true) or came back up (false) — for panel highlighting. public event Action? AddressHeldChanged; - /// - /// Flip the sign of the composed joystick Y before it reaches the device. - /// Applies to all routed sources (keys and pad) alike; panel drags write - /// the device directly and are unaffected. - /// - public bool InvertJoystickY { get; set; } - /// /// The active bindings. Assigning releases everything currently held /// (keys, toggles, pad buttons) so a reload never strands a pressed input. @@ -223,8 +216,6 @@ public sealed class InputRouter if (b.Rate <= 0f) total += Shape(_pad.Axis(b.Source), b); } - if (InvertJoystickY && axis == RioAxis.JoystickY) - total = -total; total = ClampNorm(axis, total); short raw = (short)Math.Round(total * RioAxisRange.Full(axis)); diff --git a/tests/VRio.Core.Tests/InputRouterTests.cs b/tests/VRio.Core.Tests/InputRouterTests.cs index 64d5863..2be571e 100644 --- a/tests/VRio.Core.Tests/InputRouterTests.cs +++ b/tests/VRio.Core.Tests/InputRouterTests.cs @@ -191,26 +191,6 @@ public class InputRouterTests Assert.Equal(0, _device.GetAxis(RioAxis.JoystickY)); } - [Fact] - public void InvertJoystickY_flips_the_composed_value_for_all_sources() - { - UseProfile("key Up axis JoystickY deflect 1\npadaxis LeftStickY axis JoystickY"); - _router.InvertJoystickY = true; - - _router.KeyDown("Up"); - _router.Tick(0.016); - Assert.Equal(-RioAxisRange.JoystickExtent, _device.GetAxis(RioAxis.JoystickY)); - _router.KeyUp("Up"); - - _router.SetPadState(new PadState(PadButtons.None, 0, 0.5f, 0, 0, 0, 0)); - _router.Tick(0.016); - Assert.Equal(-RioAxisRange.JoystickExtent / 2, _device.GetAxis(RioAxis.JoystickY)); - - _router.InvertJoystickY = false; // toggling back re-sends the upright value - _router.Tick(0.016); - Assert.Equal(RioAxisRange.JoystickExtent / 2, _device.GetAxis(RioAxis.JoystickY)); - } - [Fact] public void Rate_key_walks_the_throttle_and_position_sticks() { diff --git a/tests/VRio.Core.Tests/VRioDeviceTests.cs b/tests/VRio.Core.Tests/VRioDeviceTests.cs index d8ee2da..38a17c9 100644 --- a/tests/VRio.Core.Tests/VRioDeviceTests.cs +++ b/tests/VRio.Core.Tests/VRioDeviceTests.cs @@ -66,6 +66,27 @@ public class VRioDeviceTests Assert.Equal(1, device.AnalogRequests); } + [Fact] + public void InvertJoystickY_flips_the_wire_but_not_local_state() + { + var device = new VRioDevice { InvertJoystickY = true }; + var wire = new Wire(device); + device.SetAxis(RioAxis.JoystickY, 3000); + + Send(device, PacketBuilder.Build(RioCommand.AnalogRequest)); + + byte[] p = Assert.Single(wire.Packets).Payload; + Assert.Equal(-3000, AnalogCodec.Combine(p[6], p[7])); + Assert.Equal(3000, device.GetAxis(RioAxis.JoystickY)); // the panel's view is unflipped + + // Full negative deflection: -Min is one past Max, so it clamps rather than throws. + device.SetAxis(RioAxis.JoystickY, AnalogCodec.Min); + wire.Clear(); + Send(device, PacketBuilder.Build(RioCommand.AnalogRequest)); + p = Assert.Single(wire.Packets).Payload; + Assert.Equal(AnalogCodec.Max, AnalogCodec.Combine(p[6], p[7])); + } + [Fact] public void VersionRequest_reports_configured_firmware() {