From aa83dcf53e0a13beb4e58d773ab4c3570ff636f9 Mon Sep 17 00:00:00 2001 From: BANANASJIM Date: Sun, 7 Jun 2026 02:10:16 -0700 Subject: [PATCH] fix(vader5): restore back-paddle evdev codes to BTN_TRIGGER_HAPPY5-8 (real Elite 2 layout) PR #353 moved the Vader 5 back paddles M1-M4 from BTN_TRIGGER_HAPPY5-8 to HAPPY1-4 on the unverified assumption that "Steam Elite paddle slots = HAPPY1-4". That hardware check only confirmed via evtest that the codes were emitted, not that Steam recognized them as paddles. On a real Vader 5 + Steam this regressed paddle recognition: Steam stopped reading the back paddles entirely. The real Xbox Elite Series 2 (which the Vader 5 masquerades as, 045e:0b00) exposes its four back paddles P1-P4 as BTN_TRIGGER_HAPPY5-8 on Linux (xpad driver, PR #195); SDL's hidapi Xbox driver reads paddle state from that layout. Restoring M1-M4 to HAPPY5-8 was hardware-confirmed to make Steam's P1-P4 paddles respond again. This reverts only the paddle codes. The M2/M3 swap (PR #323, Steam Elite P3/P2 order) is preserved, and the non-paddle extras C/Z/LM/RM/O stay at HAPPY9-13 where PR #353 correctly moved them out of the paddle range (avoids the #235 collision). Final layout: paddles M1-M4 = HAPPY5-8, extras = HAPPY9-13, no collision. Tests: - validate_e2e_test.zig: positive lock now asserts M1-M4 = HAPPY5/7/6/8; structural-invariant asserts paddle range HAPPY5-8 and C/Z/LM/RM/O not in it. - uhid_descriptor.zig: Vader 5 descriptor test re-derives expected HID usages (HAPPY5-8 -> 21/23/22/24) and asserts M1-M4 = HAPPY5/7/6/8. - Docker test suite passes; falsifiability verified (M1=HAPPY1 fails both locks). --- devices/flydigi/vader5.toml | 11 ++++++----- src/io/uhid_descriptor.zig | 11 ++++++----- src/test/validate_e2e_test.zig | 16 ++++++++-------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/devices/flydigi/vader5.toml b/devices/flydigi/vader5.toml index 25c3225..8e24c87 100644 --- a/devices/flydigi/vader5.toml +++ b/devices/flydigi/vader5.toml @@ -112,11 +112,12 @@ Start = "BTN_START" Home = "BTN_MODE" LS = "BTN_THUMBL" RS = "BTN_THUMBR" -M1 = "BTN_TRIGGER_HAPPY1" -# Steam Xbox Elite order: usage 22 = P3, usage 23 = P2; swap M2/M3 so Vader M2 lands at P3. -M2 = "BTN_TRIGGER_HAPPY3" -M3 = "BTN_TRIGGER_HAPPY2" -M4 = "BTN_TRIGGER_HAPPY4" +M1 = "BTN_TRIGGER_HAPPY5" +# Real Elite 2 exposes back paddles P1-P4 as BTN_TRIGGER_HAPPY5-8 (Linux xpad). +# Steam reads paddle slots from that layout; M2/M3 swapped so Vader M2 lands at P3, M3 at P2. +M2 = "BTN_TRIGGER_HAPPY7" +M3 = "BTN_TRIGGER_HAPPY6" +M4 = "BTN_TRIGGER_HAPPY8" C = "BTN_TRIGGER_HAPPY9" Z = "BTN_TRIGGER_HAPPY10" LM = "BTN_TRIGGER_HAPPY11" diff --git a/src/io/uhid_descriptor.zig b/src/io/uhid_descriptor.zig index 15c7b60..4179cbb 100644 --- a/src/io/uhid_descriptor.zig +++ b/src/io/uhid_descriptor.zig @@ -1458,17 +1458,18 @@ test "descriptor: Vader 5 keeps Steam/SDL M2 and M3 paddle order" { const out = parsed.value.output orelse return error.MissingOutputSection; const buttons = out.buttons orelse return error.MissingButtons; - try testing.expectEqualStrings("BTN_TRIGGER_HAPPY1", buttons.map.get("M1") orelse return error.MissingM1); - try testing.expectEqualStrings("BTN_TRIGGER_HAPPY3", buttons.map.get("M2") orelse return error.MissingM2); - try testing.expectEqualStrings("BTN_TRIGGER_HAPPY2", buttons.map.get("M3") orelse return error.MissingM3); - try testing.expectEqualStrings("BTN_TRIGGER_HAPPY4", buttons.map.get("M4") orelse return error.MissingM4); + try testing.expectEqualStrings("BTN_TRIGGER_HAPPY5", buttons.map.get("M1") orelse return error.MissingM1); + try testing.expectEqualStrings("BTN_TRIGGER_HAPPY7", buttons.map.get("M2") orelse return error.MissingM2); + try testing.expectEqualStrings("BTN_TRIGGER_HAPPY6", buttons.map.get("M3") orelse return error.MissingM3); + try testing.expectEqualStrings("BTN_TRIGGER_HAPPY8", buttons.map.get("M4") orelse return error.MissingM4); const desc = try UhidDescriptorBuilder.buildFromOutput(alloc, out); defer alloc.free(desc); + // M1..M4 = HAPPY5-8 → usages 21,23,22,24 (M2/M3 swapped); C/Z/LM/RM/O = HAPPY9-13 → 25-29. try expectButtonUsages(desc, &.{ 1, 2, 4, 5, 7, 8, 12, 11, 13, 14, - 15, 17, 19, 18, 20, 25, 26, 27, 28, 29, + 15, 21, 23, 22, 24, 25, 26, 27, 28, 29, }); } diff --git a/src/test/validate_e2e_test.zig b/src/test/validate_e2e_test.zig index 4d9eae1..4d73b1b 100644 --- a/src/test/validate_e2e_test.zig +++ b/src/test/validate_e2e_test.zig @@ -240,28 +240,28 @@ test "validate: all device TOMLs have at least one report" { // --- 6. vader5 paddle slot invariants (regression for PR #235) --- -test "vader5: M1..M4 occupy BTN_TRIGGER_HAPPY1..4 (Steam Elite paddle slots)" { +test "vader5: M1..M4 occupy BTN_TRIGGER_HAPPY5..8 (real Elite 2 paddle slots)" { const allocator = testing.allocator; const parsed = try device_mod.parseFile(allocator, "devices/flydigi/vader5.toml"); defer parsed.deinit(); const buttons = parsed.value.output.?.buttons.?.map; - try testing.expectEqualStrings("BTN_TRIGGER_HAPPY1", buttons.get("M1") orelse return error.MissingM1); - try testing.expectEqualStrings("BTN_TRIGGER_HAPPY3", buttons.get("M2") orelse return error.MissingM2); - try testing.expectEqualStrings("BTN_TRIGGER_HAPPY2", buttons.get("M3") orelse return error.MissingM3); - try testing.expectEqualStrings("BTN_TRIGGER_HAPPY4", buttons.get("M4") orelse return error.MissingM4); + try testing.expectEqualStrings("BTN_TRIGGER_HAPPY5", buttons.get("M1") orelse return error.MissingM1); + try testing.expectEqualStrings("BTN_TRIGGER_HAPPY7", buttons.get("M2") orelse return error.MissingM2); + try testing.expectEqualStrings("BTN_TRIGGER_HAPPY6", buttons.get("M3") orelse return error.MissingM3); + try testing.expectEqualStrings("BTN_TRIGGER_HAPPY8", buttons.get("M4") orelse return error.MissingM4); } -test "vader5: C/Z/LM/RM/O do not occupy BTN_TRIGGER_HAPPY1..4" { +test "vader5: C/Z/LM/RM/O do not occupy BTN_TRIGGER_HAPPY5..8" { const allocator = testing.allocator; const parsed = try device_mod.parseFile(allocator, "devices/flydigi/vader5.toml"); defer parsed.deinit(); const buttons = parsed.value.output.?.buttons.?.map; const paddle_slots = [_][]const u8{ - "BTN_TRIGGER_HAPPY1", "BTN_TRIGGER_HAPPY2", - "BTN_TRIGGER_HAPPY3", "BTN_TRIGGER_HAPPY4", + "BTN_TRIGGER_HAPPY5", "BTN_TRIGGER_HAPPY6", + "BTN_TRIGGER_HAPPY7", "BTN_TRIGGER_HAPPY8", }; const face_extras = [_][]const u8{ "C", "Z", "LM", "RM", "O" };