fix: retain the lower player slot when joining Joy-Cons
This commit is contained in:
parent
06a89b5ae1
commit
b0a896997b
9 changed files with 61 additions and 1 deletions
|
|
@ -678,6 +678,13 @@ all chord buttons must release before retry. Confirmation is bounded to the
|
||||||
participating halves. Disconnect/reuse or a changed saved default clears the
|
participating halves. Disconnect/reuse or a changed saved default clears the
|
||||||
override, without writing a per-pair preference.
|
override, without writing a per-pair preference.
|
||||||
|
|
||||||
|
Explicit gesture joins retain the lower participating player slot rather than
|
||||||
|
always retaining the left half's slot. A regression reproduced the old player-2
|
||||||
|
result with the left half ready first but occupying the higher transport/player
|
||||||
|
index. Both ready orders and an earlier unrelated player are covered. Hardware
|
||||||
|
with right on player 1 and left on player 2 now joins on player 1; all profile
|
||||||
|
banks remain unchanged.
|
||||||
|
|
||||||
All 349 tests and all five builds passed. Real hardware exercised both live
|
All 349 tests and all five builds passed. Real hardware exercised both live
|
||||||
mode transitions, Individual boot persistence, physical join/split pulses,
|
mode transitions, Individual boot persistence, physical join/split pulses,
|
||||||
unchanged configuration generations during gestures, and automatic default
|
unchanged configuration generations during gestures, and automatic default
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,9 @@ uv run switch-pico-config joycon-mode paired --json
|
||||||
pulse, then **release all four buttons**.
|
pulse, then **release all four buttons**.
|
||||||
|
|
||||||
Two solo halves join; an existing pair splits. Other controllers are untouched.
|
Two solo halves join; an existing pair splits. Other controllers are untouched.
|
||||||
|
An explicit join keeps the lower of the two participating player slots, not
|
||||||
|
whichever slot belongs to the left half. Both LEDs follow that retained slot;
|
||||||
|
the other slot is neutralized. It never takes a slot from an unrelated player.
|
||||||
The shortcut does not change the saved default: its connection-only override
|
The shortcut does not change the saved default: its connection-only override
|
||||||
ends when either participating half disconnects, or when the adapter default
|
ends when either participating half disconnects, or when the adapter default
|
||||||
changes. Existing solo/pair banks and active selections are reused.
|
changes. Existing solo/pair banks and active selections are reused.
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -2665,8 +2665,14 @@ void process_joycon_gestures(uint32_t now_ms) {
|
||||||
if (!mature) continue;
|
if (!mature) continue;
|
||||||
// Latch success AND failure before any enrollment I/O. A failed seed
|
// Latch success AND failure before any enrollment I/O. A failed seed
|
||||||
// must not retry at the timer cadence or undo either participant.
|
// must not retry at the timer cadence or undo either participant.
|
||||||
|
// Player-slot ownership is independent of physical handedness.
|
||||||
|
// Keep the lower occupied slot, even when the left half owns the higher one.
|
||||||
|
const bool keep_left_slot = owner_slot < right_slot;
|
||||||
const bool changed = joining
|
const bool changed = joining
|
||||||
? merge_joycon_slots(owner_slot, right_slot, right, true)
|
? merge_joycon_slots(
|
||||||
|
keep_left_slot ? owner_slot : right_slot,
|
||||||
|
keep_left_slot ? right_slot : owner_slot,
|
||||||
|
keep_left_slot ? right : gesture.device, true)
|
||||||
: split_joycon_slot(static_cast<uint8_t>(owner_slot), true);
|
: split_joycon_slot(static_cast<uint8_t>(owner_slot), true);
|
||||||
if (changed) recompute_connection_status();
|
if (changed) recompute_connection_status();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1679,6 +1679,41 @@ void test_switch2_gesture_timing() {
|
||||||
"connection gestures must never write the saved adapter preference");
|
"connection gestures must never write the saved adapter preference");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_switch2_gesture_slot_order(bool left_first, uint8_t lower_slot) {
|
||||||
|
runtime_configuration.joycon_mode = JoyConMode::kIndividual;
|
||||||
|
start_backend();
|
||||||
|
auto ordinary = device(0, true, UNI_BT_CONN_PROTOCOL_BR_EDR);
|
||||||
|
auto left = switch2_device(lower_slot + 1, UNI_SW2_JOYCON_L_PID);
|
||||||
|
auto right = switch2_device(lower_slot, UNI_SW2_JOYCON_R_PID);
|
||||||
|
if (lower_slot != 0) ready_switch2(ordinary);
|
||||||
|
// Transport indices can put the left half in the higher player slot,
|
||||||
|
// even when its ready callback arrives first.
|
||||||
|
ready_switch2(left_first ? left : right);
|
||||||
|
ready_switch2(left_first ? right : left);
|
||||||
|
const auto lower_before = slot_snapshot(lower_slot);
|
||||||
|
const auto higher_before = slot_snapshot(lower_slot + 1);
|
||||||
|
const auto unrelated_before = slot_snapshot(0);
|
||||||
|
hold_gesture(left, right, 2000);
|
||||||
|
require(live_pair_slot(left, right) == lower_slot &&
|
||||||
|
!slot_snapshot(lower_slot + 1).active,
|
||||||
|
"gesture join must retain the lower participating player slot regardless of side or ready order");
|
||||||
|
const auto joined = slot_snapshot(lower_slot);
|
||||||
|
require_pair_owner(joined.identity, left, right);
|
||||||
|
require(joined.connection_generation != lower_before.connection_generation &&
|
||||||
|
slot_snapshot(lower_slot + 1).connection_generation != higher_before.connection_generation &&
|
||||||
|
slot_snapshot(lower_slot + 1).state.left_stick_x == 0 &&
|
||||||
|
slot_snapshot(lower_slot + 1).state.right_stick_x == 0 &&
|
||||||
|
left.player_leds == (1u << lower_slot) &&
|
||||||
|
right.player_leds == (1u << lower_slot),
|
||||||
|
"join must invalidate old player epochs, neutralize the retired slot and light both halves for the retained player");
|
||||||
|
if (lower_slot != 0) {
|
||||||
|
require(slot_snapshot(0).active &&
|
||||||
|
slot_snapshot(0).connection_generation == unrelated_before.connection_generation &&
|
||||||
|
controller_identity_equal(slot_snapshot(0).identity, unrelated_before.identity),
|
||||||
|
"gesture join must not take an earlier slot occupied by an unrelated controller");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void test_switch2_gesture_stale() {
|
void test_switch2_gesture_stale() {
|
||||||
runtime_configuration.joycon_mode = JoyConMode::kIndividual;
|
runtime_configuration.joycon_mode = JoyConMode::kIndividual;
|
||||||
start_backend();
|
start_backend();
|
||||||
|
|
@ -5377,6 +5412,12 @@ int main(int argc, char** argv) {
|
||||||
test_switch2_hd_pro();
|
test_switch2_hd_pro();
|
||||||
} else if (scenario == "switch2-gesture-timing") {
|
} else if (scenario == "switch2-gesture-timing") {
|
||||||
test_switch2_gesture_timing();
|
test_switch2_gesture_timing();
|
||||||
|
} else if (scenario == "switch2-gesture-slot-left-first") {
|
||||||
|
test_switch2_gesture_slot_order(true, 0);
|
||||||
|
} else if (scenario == "switch2-gesture-slot-right-first") {
|
||||||
|
test_switch2_gesture_slot_order(false, 0);
|
||||||
|
} else if (scenario == "switch2-gesture-slot-occupied") {
|
||||||
|
test_switch2_gesture_slot_order(true, 1);
|
||||||
} else if (scenario == "switch2-gesture-stale") {
|
} else if (scenario == "switch2-gesture-stale") {
|
||||||
test_switch2_gesture_stale();
|
test_switch2_gesture_stale();
|
||||||
} else if (scenario == "switch2-gesture-clock-wrap") {
|
} else if (scenario == "switch2-gesture-clock-wrap") {
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,9 @@ def test_bluepad32_backend_lifecycle_native(tmp_path: Path) -> None:
|
||||||
"switch2-mode-seed-failure",
|
"switch2-mode-seed-failure",
|
||||||
"switch2-mode-identity-failure",
|
"switch2-mode-identity-failure",
|
||||||
"switch2-gesture-timing",
|
"switch2-gesture-timing",
|
||||||
|
"switch2-gesture-slot-left-first",
|
||||||
|
"switch2-gesture-slot-right-first",
|
||||||
|
"switch2-gesture-slot-occupied",
|
||||||
"switch2-gesture-stale",
|
"switch2-gesture-stale",
|
||||||
"switch2-gesture-clock-wrap",
|
"switch2-gesture-clock-wrap",
|
||||||
"switch2-gesture-masking-epochs",
|
"switch2-gesture-masking-epochs",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue