diff --git a/src-tauri/src/controller.rs b/src-tauri/src/controller.rs index 2f2926e..d1b45bd 100644 --- a/src-tauri/src/controller.rs +++ b/src-tauri/src/controller.rs @@ -621,6 +621,29 @@ async fn run( status_tx.send_modify(|s| s.get_mut(pod).reset_link(PodState::Searching)); } Cmd::Seen { pod, address } => { + // One link is the whole controller. + // + // The `−` pod relays its twin: connected on its own it + // delivers its own paddle and D-pad *and* the `+` + // paddle and face buttons — all ten buttons, measured + // over 445 frames on one characteristic (§2.3.1). So + // the `+` pod is not connected while the `−` pod is + // there to speak for it. It stays a fallback for the + // case where the `−` pod is absent or the rider only + // owns that half. + // + // Connecting both is what the pair-merge in `Buttons` + // exists to paper over, and it is also the + // configuration in which the `−` pod stops reporting + // its own paddle — the failure that cost an evening. + // Not opening the second link removes both. + if pod == PodId::Plus && !slot_mut(&mut minus, &mut plus, PodId::Minus).idle() + { + tracing::debug!( + "controller: + pod seen but the − pod is already speaking for it" + ); + continue; + } let slot = slot_mut(&mut minus, &mut plus, pod); if !slot.auto || !slot.idle() { continue; @@ -715,6 +738,27 @@ async fn run( } _ = housekeeping.tick() => { + // Converge on one link. The `+` pod may have connected first — + // it is the one the rider happened to wake — and once the `−` + // pod is up it speaks for both, so the second link is redundant + // and is exactly the configuration that breaks the `−` paddle. + // Dropping it leaves `auto` alone, so if the `−` pod later goes + // away the `+` pod is picked up again on its next advertisement. + if !minus.idle() && plus.client.is_some() { + tracing::info!( + "controller: − pod is up and relays the pair; closing the redundant + link" + ); + forget(PodId::Plus, &mut buttons, &input_tx); + plus.generation += 1; + plus.events = None; + plus.last_seen = None; + plus.connected_at = None; + if let Some(client) = plus.client.take() { + tokio::spawn(async move { client.shutdown().await }); + } + status_tx.send_modify(|s| s.get_mut(PodId::Plus).reset_link(PodState::Idle)); + } + // A link that is plainly alive and has never carried a button // is the wedged session from §7.1, and no amount of waiting // fixes it — the pod answers the handshake and streams battery