Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion include/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ enum CONFIG_INDEX {
#define CONFIG_OPL_ENABLE_COVERART "enable_coverart"
#define CONFIG_OPL_ENABLE_BGART "enable_bgart"
#define CONFIG_OPL_ENABLE_ART_TAR "enable_art_tar"
#define CONFIG_OPL_ENABLE_ANALOG_NAV "enable_analog_nav"
#define CONFIG_OPL_ART_DELAY "art_delay"
#define CONFIG_OPL_WIDESCREEN "wide_screen"
#define CONFIG_OPL_DEFAULT_GAME_VIEW "default_game_view"
Expand Down
1 change: 0 additions & 1 deletion include/dialogs.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ enum UI_ITEMS {
UICFG_COVERART,
UICFG_ENABLE_BGART,
UICFG_ENABLE_ART_TAR,
UICFG_ENABLE_ANALOG_NAV,
UICFG_ART_DELAY,
UICFG_WIDESCREEN,
UICFG_AUTOREFRESH,
Expand Down
4 changes: 2 additions & 2 deletions include/pad.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ void unloadPads();
// Colors is on (gui.c / dia.c). Field semantics live with the writers in pad.c.
typedef struct
{
unsigned int readMisses; // total failed ready-state pad reads
unsigned int missBurst; // current consecutive-miss run, max across pads (polls)
unsigned int readMisses; // polls where at least one ready-state pad produced no fresh sample
unsigned int missBurst; // current consecutive run of such polls (0 when all ready pads read)
unsigned int missBurstMax; // session peak of the above
unsigned int pollMaxMs; // worst readPads() period this session
unsigned int stateFlaps; // DISCONN -> ready reconnect edges seen
Expand Down
5 changes: 0 additions & 5 deletions src/dialogs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1624,11 +1624,6 @@ struct UIItem diaControllerConfig[] = {
{UI_ENUM, CFG_YSENSITIVITY, 1, 1, -1, 0, 0, {.intvalue = {0, 0}}},
{UI_BREAK},

{UI_LABEL, 0, 1, 1, -1, -40, 0, {.label = {"Left Stick Navigation", -1}}},
{UI_SPACER},
{UI_BOOL, UICFG_ENABLE_ANALOG_NAV, 1, 1, -1, 0, 0, {.intvalue = {0, 0}}},
{UI_BREAK},

// Menu Rumble moved here from the old General Settings (diaConfig) by the layout restructure.
{UI_LABEL, 0, 1, 1, -1, -40, 0, {.label = {NULL, _STR_RUMBLE}}},
{UI_SPACER},
Expand Down
4 changes: 0 additions & 4 deletions src/opl.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ int gEnableNotifications;
int gEnableArt;
int gEnableBGArt;
int gEnableArtTar;
int gEnableAnalogNav;
int gArtDelay;
int gWideScreen;
int gDefaultGameView;
Expand Down Expand Up @@ -1677,7 +1676,6 @@ static void _loadConfig()
configGetInt(configOPL, CONFIG_OPL_ENABLE_COVERART, &gEnableArt);
configGetInt(configOPL, CONFIG_OPL_ENABLE_BGART, &gEnableBGArt);
configGetInt(configOPL, CONFIG_OPL_ENABLE_ART_TAR, &gEnableArtTar);
configGetInt(configOPL, CONFIG_OPL_ENABLE_ANALOG_NAV, &gEnableAnalogNav);
configGetInt(configOPL, CONFIG_OPL_ART_DELAY, &gArtDelay);
if (gArtDelay != 0 && gArtDelay != 2 && gArtDelay != 5 && gArtDelay != 8)
gArtDelay = 2;
Expand Down Expand Up @@ -2143,7 +2141,6 @@ static void _saveConfig()
configSetInt(configOPL, CONFIG_OPL_ENABLE_COVERART, gEnableArt);
configSetInt(configOPL, CONFIG_OPL_ENABLE_BGART, gEnableBGArt);
configSetInt(configOPL, CONFIG_OPL_ENABLE_ART_TAR, gEnableArtTar);
configSetInt(configOPL, CONFIG_OPL_ENABLE_ANALOG_NAV, gEnableAnalogNav);
configSetInt(configOPL, CONFIG_OPL_ART_DELAY, gArtDelay);
configSetInt(configOPL, CONFIG_OPL_WIDESCREEN, gWideScreen);
configSetInt(configOPL, CONFIG_OPL_DEFAULT_GAME_VIEW, gDefaultGameView);
Expand Down Expand Up @@ -3064,7 +3061,6 @@ static void setDefaults(void)
// reverted at NathanNeurotic's call -- users who want the archive turn it on. The engine fixes from
// #207 (uncapped-seek index integrity, no stat()-latch, toggle re-arm, the [48] filename bound) stay.
gEnableArtTar = 0;
gEnableAnalogNav = 1;
gArtDelay = 2;
gWideScreen = 1;
gDefaultGameView = GAME_VIEW_BOTH;
Expand Down
60 changes: 56 additions & 4 deletions src/pad.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,16 @@ static u32 oldpaddata;
// are SHOWN only when Settings -> Debug Colors is on.
static pad_diag_t padDiag;

// Per-poll read outcome, reset by readPads() and filled in by each readPad(): how many pads were
// in a ready state, how many produced a fresh sample, and the held bits carried by pads that
// were ready but produced NO sample -- a per-pad read MISS, the event the repeat loop must
// tolerate (#340). Per-pad, not per-poll: with two pads (or a PADEMU ds34 alongside a native
// pad) another pad's successful read must not mask this pad's miss. Misses cluster under SIO2
// load on real hardware; an emulator's pad never produces one.
static int pollPadsReady;
static int pollPadsRead;
static u32 pollMissedHeld;

void padGetDiag(pad_diag_t *out)
{
if (out)
Expand Down Expand Up @@ -483,6 +493,7 @@ static int readPad(struct pad_data_t *pad)
}

if (isPadReadyState(pad->state)) {
pollPadsReady++;
ret = padRead(pad->port, pad->slot, &pad->buttons); // port, slot, buttons

if (ret != 0) {
Expand Down Expand Up @@ -544,6 +555,7 @@ static int readPad(struct pad_data_t *pad)
#endif

if (padsRead > 0) {
pollPadsRead++;
newpdata = readLeftJoy(pad, newpdata);
pad->paddata = newpdata;

Expand All @@ -556,6 +568,17 @@ static int readPad(struct pad_data_t *pad)
// no successful read: baseline behavior (do not carry state)
}

if (pad->state == PAD_STATE_DISCONN && padsRead == 0) {
// Nothing is supplying this port any more (a ds34 pad that stopped reporting; native
// unplugs were already zeroed on the DISCONN transition above). Without this, the last
// sample would sit in edgedata forever and hold the repeat pause / activity stamp open.
pad->paddata = 0;
} else if (isPadReadyState(pad->state) && padsRead == 0) {
// This pad MISSED: ready state, no fresh sample. Remember its carried held bits for the
// repeat loop and the activity stamp.
pollMissedHeld |= pad->paddata;
}

edgedata |= pad->paddata;

return rcode;
Expand Down Expand Up @@ -817,11 +840,34 @@ int readPads()
if (time_since_last > padDiag.pollMaxMs)
padDiag.pollMaxMs = time_since_last;

pollPadsReady = 0;
pollPadsRead = 0;
pollMissedHeld = 0;
for (i = 0; i < pad_count; ++i)
result |= readPad(&pad_data[i]);

// A read MISS poll: at least one ready pad produced no fresh sample (per-pad accounting --
// another pad reading fine does not hide it). During a miss the missing pad's held bits are
// absent from paddata while pollMissedHeld/edgedata carry its last valid sample. The counters
// feed the Debug-Colors HUD line; they had no writers between the PR #328 revert and this
// fix, so the HUD showed miss:0 regardless of what the hardware was doing.
if (pollPadsRead < pollPadsReady) {
padDiag.readMisses++;
padDiag.missBurst++;
if (padDiag.missBurst > padDiag.missBurstMax)
padDiag.missBurstMax = padDiag.missBurst;
} else {
// Every ready pad read (or no pads are connected at all): no miss run in progress.
padDiag.missBurst = 0;
}

// Stamp input activity AFTER the merge: any held button/stick re-arms the PAD_SELF_HEAL_IDLE_MS gate.
if (paddata != 0)
// A missing pad's carried held sample counts as activity too -- the user is almost certainly
// still holding, and the misses themselves cluster exactly when an inline initializePad (250-600 ms
// GUI blackout) would hurt the most (#271/#272). Known residual: a pad wedged forever in a
// ready state with a held last sample keeps the self-heal deferred for the session -- accepted,
// since an EE-side re-init cannot fix a starved IOP-side driver anyway.
if (paddata != 0 || pollMissedHeld != 0)
lastInputActivityMs = curtime;

// Rumble duration is millisecond-based because some paths poll twice in one frame.
Expand Down Expand Up @@ -849,12 +895,18 @@ int readPads()
pad->rumbleOn = 0;
}

// Simple baseline repeat handling (wOPL-style): decrement per-key counters when held,
// otherwise reset to the initial delay. This removes read-miss carry/pausing behavior.
// Hold-to-repeat handling. Baseline (wOPL/official) semantics reset a key's countdown to the
// full 3x initial delay whenever the key reads unpressed -- but on a read MISS the key did not
// read at all, so that reset turned every transient SIO2 miss into a 300-1500 ms repeat stall
// while holding a direction on real hardware (#272/#340: the "hangs on the 3rd item" pattern;
// the pause was HW-validated at Beta-3442 and lost in the PR #328 revert). PAUSE the countdown
// instead while a MISSING pad's carried sample still holds the key: a key genuinely released
// during a blind window resets on the first good read, when the carry drops it. On an emulator
// (no misses) this loop is byte-for-byte the baseline behavior.
for (i = 0; i < 16; ++i) {
if (getKeyPressed(i + 1))
delaycnt[i] -= (int)time_since_last;
else
else if (!(pollMissedHeld & keyToPad[i + 1]))
delaycnt[i] = getKeyDelay(i + 1, 0);
}

Expand Down