From 08cf5a854b668ca2489b939fa4956aadae91f3e9 Mon Sep 17 00:00:00 2001 From: "NathanNeurotic (Ripto)" <109461996+NathanNeurotic@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:19:51 -0700 Subject: [PATCH] fix(gui): stop discarding presses made during a screen transition Press a direction right after switching screens and nothing happens. Found while investigating #271; verified in code, and separate from that report. guiMainLoop() polls the pads EVERY frame (gui.c) but only runs screenHandler->handleInput() when no transition is in flight. A fade is 26 frames -- about 430 ms at 60 Hz -- and every poll during it still advanced the key-on baseline, so the edge was consumed by a poll with nothing listening behind it. The press is simply gone. FIX. padFreezeEdgeBaseline() holds the edge baseline while a transition is running, so a key-on raised during the fade is still pending when the new screen takes over. Freezing the BASELINE rather than skipping the poll is deliberate: readPads() also expires rumble taps, drives the pad state machine and detects (dis)connects, so not calling it during a transition would leave a motor spinning and a replug unnoticed. Scope: armed ONLY while screenHandlerTarget is set. Ordinary same-screen navigation is untouched, deliberately -- the #271/#272 read-miss work is on hardware test right now and this must not perturb what is being measured. Residual: a press that is also RELEASED inside the fade is still lost, since the edge view reflects the current sample by then. Fixing that needs a sticky pending-press latch, which is the correct long-term design for this input layer but changes edge consumption everywhere and should not land mid-test. Co-Authored-By: Claude Opus 5 --- include/pad.h | 4 ++++ src/gui.c | 5 +++++ src/pad.c | 30 +++++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/include/pad.h b/include/pad.h index 7866abef9..625973978 100644 --- a/include/pad.h +++ b/include/pad.h @@ -22,6 +22,10 @@ int startPads(); int readPads(); +// While frozen, readPads() keeps sampling but does not advance the key-on baseline, so an edge +// raised now survives until it is consumed. Used across screen transitions, where the GUI polls +// every frame but runs no input handler -- without this, presses made during a fade are discarded. +void padFreezeEdgeBaseline(int freeze); void unloadPads(); // Menu rumble (#172), gated by gEnableRumble. Tap/Bump arm a pulse on every capable pad and never diff --git a/src/gui.c b/src/gui.c index 6d07e6fff..1f3a2b888 100644 --- a/src/gui.c +++ b/src/gui.c @@ -2339,6 +2339,11 @@ static void guiDrawOverlays() static void guiReadPads() { + // A transition means handleInput() will not run this frame (see guiMainLoop). Hold the key-on + // baseline so a press made during the ~430ms fade is still pending when the new screen takes + // over, instead of being consumed by a poll that nobody is listening behind. + padFreezeEdgeBaseline(screenHandlerTarget != NULL); + if (readPads()) guiInactiveFrames = 0; else { diff --git a/src/pad.c b/src/pad.c index 2c0e42e0d..4b3cc1ec1 100644 --- a/src/pad.c +++ b/src/pad.c @@ -186,6 +186,31 @@ static u32 oldpaddata; static u32 edgedata; static u32 oldedgedata; +/* + While set, readPads() keeps sampling but does NOT advance the edge baseline, so a key-on raised + during this window survives until someone is actually listening. + + guiMainLoop() polls the pads EVERY frame (gui.c) but only runs screenHandler->handleInput() when no + screen transition is in flight. A fade is 26 frames -- about 430 ms at 60 Hz -- and every poll in + that window still shifted oldedgedata, so the edge was consumed by a poll with no consumer behind + it. Any button pressed during a screen fade was silently discarded: press a direction right after + switching screens and nothing happens. + + Freezing the BASELINE rather than skipping the poll is deliberate. readPads() also expires rumble + taps, drives the pad state machine and detects (dis)connects, so simply not calling it during a + transition would leave a motor spinning and a replug unnoticed. + + Scope note: this is armed only while a transition is running, so it cannot affect ordinary + same-screen navigation -- deliberately, so it does not disturb the #271/#272 read-miss work being + tested on hardware. +*/ +static int edgeBaselineFrozen = 0; + +void padFreezeEdgeBaseline(int freeze) +{ + edgeBaselineFrozen = freeze ? 1 : 0; +} + static int delaycnt[16]; static int paddelay[16]; @@ -965,7 +990,10 @@ int readPads() paddata = 0; // Same one-poll lifetime as paddata, so an edge still lasts exactly one poll and cannot re-fire; // the difference is only in how a MISSED read contributes below (see the edgedata note above). - oldedgedata = edgedata; + // Unless the baseline is frozen -- then a pending key-on is held for whoever is not listening yet + // (padFreezeEdgeBaseline; screen transitions). + if (!edgeBaselineFrozen) + oldedgedata = edgedata; edgedata = 0; /*