From 007d15e668464420d9461ad936f464020413dcbc Mon Sep 17 00:00:00 2001 From: Cyd Date: Tue, 7 Jul 2026 09:47:25 -0500 Subject: [PATCH] Wire carries stick-up as negative; Invert Y opts back to physical The old Invert joystick Y box (checked by default) is now the wire's native convention: full up = -80 in AnalogReply with the flag off. Checking the renamed Invert Y sends the physical direction (up = +80). Control strip cleanup: test-mode buttons and the firmware selector are gone (the device still reports 4.2), Keyboard/Gamepad/Invert Y share one line, the help text drops the bindings sentence, and the wire log gets the reclaimed height. Co-Authored-By: Claude Fable 5 --- src/VRio.App/MainForm.cs | 57 +++++++++--------------- src/VRio.Core/Device/VRioDevice.cs | 15 ++++--- tests/VRio.Core.Tests/VRioDeviceTests.cs | 20 +++++++-- 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/VRio.App/MainForm.cs b/src/VRio.App/MainForm.cs index a4b34fd..2b75143 100644 --- a/src/VRio.App/MainForm.cs +++ b/src/VRio.App/MainForm.cs @@ -41,35 +41,31 @@ internal sealed class MainForm : Form ForeColor = Color.Gray, }; - private readonly NumericUpDown _verMajor = new() { Location = new Point(80, 24), Width = 44, Minimum = 0, Maximum = 127, Value = 4 }; - private readonly NumericUpDown _verMinor = new() { Location = new Point(140, 24), Width = 44, Minimum = 0, Maximum = 127, Value = 2 }; - private readonly CheckBox _spring = new() { Text = "Stick springs back to center", Location = new Point(10, 56), AutoSize = true, Checked = true }; - private readonly Button _centerAxes = new() { Text = "Center all axes", Location = new Point(10, 82), Width = 140, Height = 26 }; - private readonly Button _lampsOff = new() { Text = "All lamps off", Location = new Point(156, 82), Width = 140, Height = 26 }; - private readonly Button _testEnter = new() { Text = "Enter test mode", Location = new Point(10, 114), Width = 140, Height = 26 }; - private readonly Button _testExit = new() { Text = "Exit test mode", Location = new Point(156, 114), Width = 140, Height = 26 }; + private readonly CheckBox _spring = new() { Text = "Stick springs back to center", Location = new Point(10, 24), AutoSize = true, Checked = true }; + private readonly Button _centerAxes = new() { Text = "Center all axes", Location = new Point(10, 52), Width = 140, Height = 26 }; + private readonly Button _lampsOff = new() { Text = "All lamps off", Location = new Point(156, 52), Width = 140, Height = 26 }; private readonly CheckBox _kbInput = new() { Text = "Keyboard", Location = new Point(10, 22), AutoSize = true, Checked = true }; - private readonly CheckBox _padInput = new() { Text = "Xbox gamepad", Location = new Point(120, 22), AutoSize = true, Checked = true }; - private readonly CheckBox _invertY = new() { Text = "Invert joystick Y", Location = new Point(10, 46), AutoSize = true, Checked = true }; + private readonly CheckBox _padInput = new() { Text = "Gamepad", Location = new Point(100, 22), AutoSize = true, Checked = true }; + private readonly CheckBox _invertY = new() { Text = "Invert Y", Location = new Point(190, 22), AutoSize = true }; private readonly Label _padStatus = new() { Text = "No controller detected.", - Location = new Point(10, 70), + Location = new Point(10, 46), AutoSize = true, ForeColor = Color.Gray, }; - private readonly Button _reloadBindings = new() { Text = "Reload bindings", Location = new Point(10, 92), Width = 140, Height = 26 }; - private readonly Button _editBindings = new() { Text = "Edit bindings…", Location = new Point(156, 92), Width = 140, Height = 26 }; + private readonly Button _reloadBindings = new() { Text = "Reload bindings", Location = new Point(10, 68), Width = 140, Height = 26 }; + private readonly Button _editBindings = new() { Text = "Edit bindings…", Location = new Point(156, 68), Width = 140, Height = 26 }; private readonly CheckBox _kbLights = new() { Text = "Mirror lamps on RGB keyboard (Dynamic Lighting)", - Location = new Point(10, 124), + Location = new Point(10, 100), AutoSize = true, }; private readonly ComboBox _kbLightsTarget = new() { - Location = new Point(10, 146), + Location = new Point(10, 122), Width = 286, DropDownStyle = ComboBoxStyle.DropDownList, Enabled = false, // populated while the mirror is on @@ -77,12 +73,12 @@ internal sealed class MainForm : Form private readonly Label _kbInputLabel = new() { Text = "Capture keyboard (no window focus needed):", - Location = new Point(10, 168), + Location = new Point(10, 144), AutoSize = true, }; private readonly ComboBox _kbInputTarget = new() { - Location = new Point(10, 186), + Location = new Point(10, 162), Width = 286, DropDownStyle = ComboBoxStyle.DropDownList, }; @@ -95,25 +91,24 @@ internal sealed class MainForm : Form private readonly Label _counters = new() { - Location = new Point(12, 458), + Location = new Point(12, 372), AutoSize = true, Font = new Font("Consolas", 8f), }; private readonly Label _help = new() { - Location = new Point(12, 514), + Location = new Point(12, 428), MaximumSize = new Size(306, 0), AutoSize = true, ForeColor = Color.Gray, Text = "Left-click a cell: momentary press. Right-click: latch it down. " + - "Drag the X/Y box and the Z / L / R gauges to move the axes. " + - "Keyboard and Xbox-pad input follow the bindings file (Edit bindings…).", + "Drag the X/Y box and the Z / L / R gauges to move the axes.", }; private readonly TextBox _logBox = new() { - Location = new Point(12, 598), + Location = new Point(12, 498), Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Both, // long wire lines don't wrap — scroll to read @@ -184,8 +179,6 @@ internal sealed class MainForm : Form _rescan.Click += (_, _) => RefreshPorts(); _openClose.Click += (_, _) => ToggleOpen(); - _verMajor.ValueChanged += (_, _) => _device.VersionMajor = (byte)_verMajor.Value; - _verMinor.ValueChanged += (_, _) => _device.VersionMinor = (byte)_verMinor.Value; _spring.CheckedChanged += (_, _) => _canvas.StickSpringsBack = _spring.Checked; _centerAxes.Click += (_, _) => { @@ -198,8 +191,6 @@ internal sealed class MainForm : Form _device.ClearLamps(); _canvas.Invalidate(); }; - _testEnter.Click += (_, _) => _device.SendTestMode(1); - _testExit.Click += (_, _) => _device.SendTestMode(0); _clearLog.Click += (_, _) => _logBox.Clear(); _kbInput.CheckedChanged += (_, _) => @@ -280,21 +271,17 @@ internal sealed class MainForm : Form panel.Controls.Add(_openClose); panel.Controls.Add(_linkStatus); - var device = new GroupBox { Text = "Device", Location = new Point(12, 68), Size = new Size(306, 150) }; - device.Controls.Add(new Label { Text = "Firmware:", Location = new Point(10, 27), AutoSize = true }); - device.Controls.Add(_verMajor); - device.Controls.Add(new Label { Text = ".", Location = new Point(127, 27), AutoSize = true }); - device.Controls.Add(_verMinor); - device.Controls.AddRange(new Control[] { _spring, _centerAxes, _lampsOff, _testEnter, _testExit }); + var device = new GroupBox { Text = "Device", Location = new Point(12, 68), Size = new Size(306, 88) }; + device.Controls.AddRange(new Control[] { _spring, _centerAxes, _lampsOff }); panel.Controls.Add(device); - var input = new GroupBox { Text = "Input", Location = new Point(12, 224), Size = new Size(306, 226) }; + var input = new GroupBox { Text = "Input", Location = new Point(12, 162), Size = new Size(306, 202) }; input.Controls.AddRange(new Control[] { _kbInput, _padInput, _invertY, _padStatus, _reloadBindings, _editBindings, _kbLights, _kbLightsTarget, _kbInputLabel, _kbInputTarget }); panel.Controls.Add(input); panel.Controls.Add(_counters); panel.Controls.Add(_help); - panel.Controls.Add(new Label { Text = "Wire log:", Location = new Point(12, 580), AutoSize = true }); + panel.Controls.Add(new Label { Text = "Wire log:", Location = new Point(12, 480), AutoSize = true }); _logBox.Size = new Size(306, ClientSize.Height - _logBox.Top - 44); panel.Controls.Add(_logBox); @@ -359,11 +346,11 @@ internal sealed class MainForm : Form /// /// Keys route to the panel unless the user is in a control that needs - /// them (port list, firmware spinners, log box scrolling). + /// them (port list, log box scrolling). /// private bool KeyboardRoutingActive => _kbInput.Checked && - !_portBox.ContainsFocus && !_verMajor.ContainsFocus && !_verMinor.ContainsFocus && !_logBox.ContainsFocus; + !_portBox.ContainsFocus && !_logBox.ContainsFocus; protected override void OnHandleCreated(EventArgs e) { diff --git a/src/VRio.Core/Device/VRioDevice.cs b/src/VRio.Core/Device/VRioDevice.cs index c9994a6..a871d35 100644 --- a/src/VRio.Core/Device/VRioDevice.cs +++ b/src/VRio.Core/Device/VRioDevice.cs @@ -58,9 +58,11 @@ public sealed class VRioDevice 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. + /// The wire natively carries stick-up as negative (full up = −80 in + /// AnalogReply); set this to send the physical direction (up = positive) + /// instead. Only the host sees the difference — local state + /// (, the panel's dot) keeps the physical stick + /// direction either way. /// public bool InvertJoystickY { get; set; } @@ -110,7 +112,8 @@ public sealed class VRioDevice /// /// Axis value as the next AnalogReply will carry it — same as - /// except for the flip. + /// except joystick Y follows the wire convention + /// (see ). /// public short GetWireAxis(RioAxis axis) { @@ -119,8 +122,8 @@ public sealed class VRioDevice } private short WireY(short y) => InvertJoystickY - ? (short)Math.Min(AnalogCodec.Max, -y) // clamp: -Min (8192) is one past the 14-bit Max - : y; + ? y + : (short)Math.Min(AnalogCodec.Max, -y); // clamp: -Min (8192) is one past the 14-bit Max /// /// Move an axis. Values are clamped to the 14-bit signed range the wire diff --git a/tests/VRio.Core.Tests/VRioDeviceTests.cs b/tests/VRio.Core.Tests/VRioDeviceTests.cs index c810c12..4f22fdb 100644 --- a/tests/VRio.Core.Tests/VRioDeviceTests.cs +++ b/tests/VRio.Core.Tests/VRioDeviceTests.cs @@ -61,15 +61,15 @@ public class VRioDeviceTests Assert.Equal(1000, AnalogCodec.Combine(p[0], p[1])); // throttle Assert.Equal(0, AnalogCodec.Combine(p[2], p[3])); // left pedal Assert.Equal(0, AnalogCodec.Combine(p[4], p[5])); // right pedal - Assert.Equal(3000, AnalogCodec.Combine(p[6], p[7])); // joystick Y + Assert.Equal(-3000, AnalogCodec.Combine(p[6], p[7])); // joystick Y (up is negative on the wire) Assert.Equal(-5000, AnalogCodec.Combine(p[8], p[9])); // joystick X Assert.Equal(1, device.AnalogRequests); } [Fact] - public void InvertJoystickY_flips_the_wire_but_not_local_state() + public void Joystick_Y_is_negated_on_the_wire_but_not_locally() { - var device = new VRioDevice { InvertJoystickY = true }; + var device = new VRioDevice(); var wire = new Wire(device); device.SetAxis(RioAxis.JoystickY, 3000); @@ -88,6 +88,20 @@ public class VRioDeviceTests Assert.Equal(AnalogCodec.Max, AnalogCodec.Combine(p[6], p[7])); } + [Fact] + public void InvertJoystickY_sends_the_physical_direction_instead() + { + 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.GetWireAxis(RioAxis.JoystickY)); + } + [Fact] public void VersionRequest_reports_configured_firmware() {