From 44f7c39ee1a6252857f879001714c44083286288 Mon Sep 17 00:00:00 2001 From: arcattack Date: Thu, 23 Jul 2026 00:49:41 -0500 Subject: [PATCH] Issue #24: release held pad buttons on controller disconnect (look latch) XInput button emission is edge-based against previousPadButtons; the disconnect path zeroed that memory WITHOUT emitting release edges, so any button held when a controller (wrapper) died stayed pressed game-side forever -- and the zeroing also erased what a reconnect would have released. Field signature (playtest night): a PS3-pad-in-xbox- mode wrapper died mid-hat-hold -> the look-view machine (hold-based, mppr) latched a side view with the authored look pitch ("stuck side view + pitch at ground, no recovery"); the keyboard-only run was clean. On the connected->disconnected transition PadRIO now emits EmitButton(address, 0) for every binding whose mask was held, then forgets. Not reproducible headless (needs a real hardware unplug mid-hold) -- awaiting the reporter's controller for live verification. Co-Authored-By: Claude Opus 4.8 (1M context) --- context/glass-cockpit.md | 10 ++++++++++ engine/MUNGA_L4/L4PADRIO.cpp | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/context/glass-cockpit.md b/context/glass-cockpit.md index 373bae7..89793ab 100644 --- a/context/glass-cockpit.md +++ b/context/glass-cockpit.md @@ -78,6 +78,16 @@ BEFORE Present. D3DPOOL_DEFAULT: released at every device-Reset site. One-shot `[waitscreen] paint path:` says which path is live. Verified: 14 rapid PrintWindow frames all carry the text (was alternating black). +## PadRIO gotcha: release-on-disconnect (issue #24, 2026-07-23) [T3 -- awaiting live verify] +XInput button emission is EDGE-based (`held != was_held` vs `previousPadButtons`). The +disconnect path zeroed `previousPadButtons` WITHOUT emitting release edges -- any button held +at the moment a controller (wrapper) died stayed pressed game-side FOREVER, and the zeroing +also erased the memory a reconnect would have released from. Field signature: PS3-pad-in- +xbox-mode wrapper died mid-hold -> look-view latched (stuck side view + pitch-down, no +recovery; keyboard-only run clean). Fix: on the connected->disconnected transition, emit +`EmitButton(address, 0)` for every binding whose mask was held, THEN forget. Not reproducible +headless (needs real hardware unplug) -- logic fix, awaiting the reporter's controller. + ## Plan of record (2026-07-17) Working branch `glass-cockpit` (from master `e2c21c4`). Steps: (1) gates+scaffolding [THIS], diff --git a/engine/MUNGA_L4/L4PADRIO.cpp b/engine/MUNGA_L4/L4PADRIO.cpp index 42ec24c..97b619f 100644 --- a/engine/MUNGA_L4/L4PADRIO.cpp +++ b/engine/MUNGA_L4/L4PADRIO.cpp @@ -416,6 +416,25 @@ void DEBUG_STREAM << "[padrio] XInput pad " << padIndex << " disconnected\n" << std::flush; padIndex = -1; + // issue #24 (stuck look-view): RELEASE everything the pad was + // holding BEFORE forgetting it -- zeroing previousPadButtons + // without emitting the release edges left the game-side buttons + // pressed FOREVER (a hat held at the moment a flaky controller + // wrapper died latched the look view with no recovery; field + // report: PS3-pad-in-xbox-mode, stuck side view + pitch-down). + if (previousPadButtons != 0) + { + for (int b = 0; b < bindings.padButtonBindingCount; ++b) + { + const PadBindingProfile::PadButtonBinding &binding = + bindings.padButtonBindings[b]; + if ((previousPadButtons & binding.buttonMask) != 0 + && binding.action.kind == PadBindingProfile::ActionButton) + { + EmitButton(binding.action.address, 0); + } + } + } previousPadButtons = 0; } }