Fix pedal gauges frozen under ZR mix: feed L/R from pre-mix pedal readouts

The editor's L/R gauges read Rx/Ry, but EnableZR (on in real profiles)
folds the pedals into Rz and pins Rx/Ry to center, so the bars never
moved with the physical pedals. AxisCalibrator now exposes
LeftPedalOutput/RightPedalOutput (the calibrated pre-mix positions) and
RioRuntime.AxesUpdated carries an AxisReadout (the six virtual axes +
both pedals); the strip draws L/R from the pedal readouts, Z/Rz/X/Y
from the axes. With ZR off the readouts equal Rx/Ry, so nothing
changes there. Verified by screenshot: Rx/Ry centered while L=75% and
R=25% render correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-06 10:22:05 -05:00
co-authored by Claude Fable 5
parent b2d1927c51
commit 2ac3188528
9 changed files with 94 additions and 28 deletions
@@ -70,6 +70,16 @@ public sealed class AxisCalibrator
private static int Clamp(int v) => RioJoy.Core.Compat.Net48Math.Clamp(v, 0, AxisOutputs.Max);
/// <summary>
/// The calibrated left-pedal position from the last <see cref="Update"/>
/// (<c>0..32766</c>) — always tracks the pedal, even when
/// <see cref="AxisCalibrationConfig.EnableZR"/> centers <see cref="AxisOutputs.Rx"/>.
/// </summary>
public int LeftPedalOutput => Clamp(_leftPedalLast);
/// <summary>Right-pedal counterpart of <see cref="LeftPedalOutput"/>.</summary>
public int RightPedalOutput => Clamp(_rightPedalLast);
private int Throttle(int throttle)
{
if (throttle < -RangeThrottle || throttle > RangeThrottle)
@@ -0,0 +1,27 @@
namespace RioJoy.Core.Calibration;
/// <summary>
/// One live calibration sample for gauge displays: the six virtual-joystick axis
/// outputs plus the calibrated pedal positions. The pedals are surfaced
/// separately because the ZR mix (<see cref="AxisCalibrationConfig.EnableZR"/>)
/// folds them into Rz and pins Rx/Ry to center — a pedal gauge fed from the axes
/// alone would freeze while the physical pedals move.
/// </summary>
public readonly struct AxisReadout
{
/// <summary>The six virtual-joystick outputs (what the HID device receives).</summary>
public AxisOutputs Axes { get; }
/// <summary>Calibrated left-pedal position (<c>0..32766</c>), before the ZR mix.</summary>
public int LeftPedal { get; }
/// <summary>Calibrated right-pedal position (<c>0..32766</c>), before the ZR mix.</summary>
public int RightPedal { get; }
public AxisReadout(AxisOutputs axes, int leftPedal, int rightPedal)
{
Axes = axes;
LeftPedal = leftPedal;
RightPedal = rightPedal;
}
}
+6 -5
View File
@@ -57,11 +57,11 @@ public sealed class RioRuntime : IRioCommandSink, IDisposable
public event Action<int, bool>? ButtonActivity;
/// <summary>
/// Raised with the six calibrated axis values after every analog reply —
/// independent of the joystick sink, so the editor can show live gauges even
/// while input routing is suppressed.
/// Raised with the calibrated readout (virtual axes + pre-mix pedal positions)
/// after every analog reply — independent of the joystick sink, so the editor
/// can show live gauges even while input routing is suppressed.
/// </summary>
public event Action<AxisOutputs>? AxesUpdated;
public event Action<AxisReadout>? AxesUpdated;
/// <summary>Raised with the firmware version when a <see cref="RioCommand.VersionReply"/> arrives.</summary>
public event Action<VersionInfo>? VersionReceived;
@@ -137,7 +137,8 @@ public sealed class RioRuntime : IRioCommandSink, IDisposable
// Output is best-effort: a faulty joystick sink must not kill the link.
}
AxesUpdated?.Invoke(axes);
AxesUpdated?.Invoke(new AxisReadout(
axes, _calibrator.LeftPedalOutput, _calibrator.RightPedalOutput));
}
private void OnPacket(RioPacket packet)
+7 -5
View File
@@ -32,7 +32,7 @@ internal sealed class PanelCanvas : Control
public Func<JoyStickSetting, bool>? CalibrationProvider { get; set; }
private readonly HashSet<int> _livePressed = new();
private AxisOutputs? _axes;
private AxisReadout? _axes;
public int? SelectedAddress { get; private set; }
@@ -46,14 +46,16 @@ internal sealed class PanelCanvas : Control
}
/// <summary>
/// Show the latest calibrated axis values on the encoder-gauge strip. Arrives at
/// Show the latest calibrated readout on the encoder-gauge strip. Arrives at
/// the analog poll rate (~18 Hz), so only the strip is repainted, and only when a
/// value actually changed.
/// </summary>
public void SetAxes(AxisOutputs axes)
public void SetAxes(AxisReadout axes)
{
if (_axes is { } p && p.X == axes.X && p.Y == axes.Y && p.Z == axes.Z &&
p.Rx == axes.Rx && p.Ry == axes.Ry && p.Rz == axes.Rz)
if (_axes is { } p &&
p.Axes.X == axes.Axes.X && p.Axes.Y == axes.Axes.Y && p.Axes.Z == axes.Axes.Z &&
p.Axes.Rx == axes.Axes.Rx && p.Axes.Ry == axes.Axes.Ry && p.Axes.Rz == axes.Axes.Rz &&
p.LeftPedal == axes.LeftPedal && p.RightPedal == axes.RightPedal)
return;
_axes = axes;
Invalidate(new Rectangle(0, 0, Width, PanelView.TopStrip));
+10 -9
View File
@@ -75,7 +75,7 @@ public static class PanelView
Func<int, string?> function,
Func<int, bool> lit,
Func<int, bool> live,
AxisOutputs? axes,
AxisReadout? axes,
int? selected)
{
g.Clear(Color.FromArgb(28, 28, 28));
@@ -152,7 +152,7 @@ public static class PanelView
}
}
private static void DrawEncoderStrip(Graphics g, Font font, AxisOutputs? axes)
private static void DrawEncoderStrip(Graphics g, Font font, AxisReadout? axes)
{
using var pen = new Pen(Color.FromArgb(120, 160, 120));
using var gauge = new SolidBrush(Color.FromArgb(20, 40, 20));
@@ -181,12 +181,13 @@ public static class PanelView
g.FillRectangle(gauge, box);
// Live values from the RIO's analog stream (null when the link is down).
// The gauges show the calibrated outputs — post invert/deadzone/mix — i.e.
// exactly what the virtual joystick receives.
// Z/Rz/X/Y show the virtual-joystick outputs; L and R show the calibrated
// pedal positions from before the ZR mix, which pins Rx/Ry to center — the
// pedal gauges must track the physical pedals either way.
if (axes is { } a)
{
// Z (throttle) and the L/R pedals (Rx/Ry) fill bottom-up.
foreach (var (box, value) in new[] { (zBox, a.Z), (lBox, a.Rx), (rBox, a.Ry) })
// Z (throttle) and the L/R pedals fill bottom-up.
foreach (var (box, value) in new[] { (zBox, a.Axes.Z), (lBox, a.LeftPedal), (rBox, a.RightPedal) })
{
int h = (int)Math.Round(box.Height * AxisGauges.Fraction(value));
if (h > 0)
@@ -194,7 +195,7 @@ public static class PanelView
}
// Rz (mixed rudder) deflects left/right from the bar's center.
double d = AxisGauges.Deflection(a.Rz);
double d = AxisGauges.Deflection(a.Axes.Rz);
int cx = rzBox.X + rzBox.Width / 2;
int w = (int)Math.Round(rzBox.Width / 2.0 * Math.Abs(d));
if (w > 0)
@@ -217,8 +218,8 @@ public static class PanelView
// (0 = left/top, matching the HID axis convention).
if (axes is { } live)
{
int px = xyBox.X + (int)Math.Round(AxisGauges.Fraction(live.X) * (xyBox.Width - 1));
int py = xyBox.Y + (int)Math.Round(AxisGauges.Fraction(live.Y) * (xyBox.Height - 1));
int px = xyBox.X + (int)Math.Round(AxisGauges.Fraction(live.Axes.X) * (xyBox.Width - 1));
int py = xyBox.Y + (int)Math.Round(AxisGauges.Fraction(live.Axes.Y) * (xyBox.Height - 1));
using var dot = new SolidBrush(Color.FromArgb(150, 230, 150));
g.FillEllipse(dot, px - 3, py - 3, 7, 7);
}
+2 -2
View File
@@ -466,8 +466,8 @@ public sealed class ProfileEditorForm : Form
public void ShowLiveActivity(int address, bool pressed) =>
RunOnUi(() => _canvas.SetLivePressed(address, pressed));
/// <summary>Show the live calibrated axis values on the encoder gauges (serial thread).</summary>
public void ShowAxes(AxisOutputs axes) =>
/// <summary>Show the live calibrated readout on the encoder gauges (serial thread).</summary>
public void ShowAxes(AxisReadout axes) =>
RunOnUi(() => _canvas.SetAxes(axes));
/// <summary>Show the firmware version from a RIO version reply (serial thread).</summary>