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; /*