From 78a012ce06b8756870e18230c3957c00cc2a66da Mon Sep 17 00:00:00 2001 From: quarrel07 <178681861+quarrel07@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:21:36 -0700 Subject: [PATCH] Fix the reset button doing nothing or looping the start screen when pressed repeatedly (#719) * Make the reset button reliable from any screen Pressing reset repeatedly near app start could do nothing, fade the music with no reset, or bounce the press-start screen back to itself. Three defects in the old handler: - It ran from the ImGui widget, racing the menu state machine; a press landing mid-fade was re-advanced by the in-flight transition (the press-start loop). - It always wrote gGamestateNext = MAIN_MENU_FROM_QUIT; once the game was already in that state a repeat write is swallowed by the != guard in main.c, so nothing happened. - CM_ResetAudio ran unconditionally, so the music faded even when the reset was swallowed. The widget now only sets an atomic request flag; ApplyPendingReset (top of push_frame, on the game loop) performs the reset. It alternates between the two identical FROM_QUIT gamestates so every press trips the gamestate switch, and re-enters the menus through the intro's own transition protocol (FADE_MODE_LOGO -> setup_menus rebuild + fresh fade-in), which replaces any in-flight transition. Audio only fades when a reset actually executes. The dead gSkipIntro switch in the old handler (an unconditional override below it always won) is gone; behavior is unchanged: reset lands on the logo intro, or the start menu in debug mode. Play-verified: hammering reset during the splash screens restarts the logo every press; reset mid-fade after press-start no longer loops back; resets from the main menu and mid-race work as before. * Honor gSkipIntro when picking the post-reset screen Review feedback on #719: restore the gSkipIntro switch from the old handler instead of hard-coding the logo intro, keeping the debug-mode override on top (no else), matching the boot-time logic in main(). Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 Co-authored-by: MegaMech --- src/port/Game.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++++++ src/port/Game.h | 1 + src/port/ui/Menu.h | 44 +++--------------------------- 3 files changed, 72 insertions(+), 40 deletions(-) diff --git a/src/port/Game.cpp b/src/port/Game.cpp index 8ccd382e6..1c6d49af9 100644 --- a/src/port/Game.cpp +++ b/src/port/Game.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "engine/World.h" #include "engine/AllTracks.h" @@ -911,7 +912,73 @@ void CM_ResetAudio(void) { } } +static std::atomic sResetRequested{ false }; + +void CM_RequestReset(void) { + sResetRequested.store(true); +} + +// The reset widget only requests; the reset is applied here at the top of the +// game frame. Applying it from the widget raced the menu state machine: a +// press landing mid-fade was re-advanced by the in-flight transition, and a +// repeat press rewrote the gamestate it had already set, which the != guard +// in main.c swallows while the audio fade still runs (silent no-op). +static void ApplyPendingReset() { + if (!sResetRequested.exchange(false)) { + return; + } + + // The FROM_QUIT gamestates run identical inits; alternating keeps + // gGamestateNext != gGamestate true so every press re-enters the menus. + gGamestateNext = (gGamestate == MAIN_MENU_FROM_QUIT) ? START_MENU_FROM_QUIT : MAIN_MENU_FROM_QUIT; + gIsGamePaused = 0; + // Reset credits + D_800DC5E4 = 0; + gTourComplete = false; + SetMarioRaceway(); + memset(&gGameModeMenuColumn, 0, sizeof(s8) * NUM_ROWS_GAME_MODE_MENU); + memset(&gGameModeSubMenuColumn, 0, sizeof(s8) * NUM_COLUMN_GAME_MODE_SUB_MENU * NUM_ROWS_GAME_MODE_SUB_MENU); + + CM_ResetAudio(); + + // Close the editor. + if (gEditor.IsEnabled()) { + gEditor.Disable(); + } + + // Set the debug menu track browsing index back to zero + TrackBrowser::Instance->Reset(); + + // Land on the same screen the gSkipIntro setting picks at boot. + switch (CVarGetInteger("gSkipIntro", 0)) { + case 0: + gMenuSelection = HARBOUR_MASTERS_MENU; + break; + case 1: + gMenuSelection = LOGO_INTRO_MENU; + break; + case 2: + gMenuSelection = START_MENU; + break; + case 3: + gMenuSelection = MAIN_MENU; + break; + } + + // Debug mode override gSkipIntro + if (CVarGetInteger("gEnableDebugMode", 0) == true) { + gMenuSelection = START_MENU; + } + // Re-enter through the intro's own transition protocol (see HM_TickIntro): + // FADE_MODE_LOGO makes setup_menus rebuild the menu items and start a + // fresh fade-in, replacing any in-flight transition that would otherwise + // advance the stale screen right after the reset. + gMenuFadeType = 0; + gFadeModeSelection = FADE_MODE_LOGO; +} + void push_frame() { + ApplyPendingReset(); GameEngine::StartAudioFrame(); GameEngine::Instance->StartFrame(); thread5_iteration(); diff --git a/src/port/Game.h b/src/port/Game.h index 63ad92a4b..edd1902d8 100644 --- a/src/port/Game.h +++ b/src/port/Game.h @@ -35,6 +35,7 @@ extern Registry gActorRegistry; extern Registry gItemRegistry; extern DataRegistry gItemTableRegistry; World* GetWorld(void); // Retrieve the world instance +void CM_RequestReset(void); // Queue a game reset; applied at the top of the next game frame #endif // NOLINTBEGIN(readability-identifier-naming) diff --git a/src/port/ui/Menu.h b/src/port/ui/Menu.h index f3b2fd3dd..7e84e5d26 100644 --- a/src/port/ui/Menu.h +++ b/src/port/ui/Menu.h @@ -62,46 +62,10 @@ class Menu : public GuiWindow { "Searches all menus for the given text, including tooltips.")) } } } }; virtual void ProcessReset() { - gGamestateNext = MAIN_MENU_FROM_QUIT; - gIsGamePaused = 0; - // Reset credits - D_800DC5E4 = 0; - gTourComplete = false; - SetMarioRaceway(); - memset(&gGameModeMenuColumn, 0, sizeof(s8) * NUM_ROWS_GAME_MODE_MENU); - memset(&gGameModeSubMenuColumn, 0, sizeof(s8) * NUM_COLUMN_GAME_MODE_SUB_MENU * NUM_ROWS_GAME_MODE_SUB_MENU); - - CM_ResetAudio(); - - switch(CVarGetInteger("gSkipIntro", 0)) { - case 0: - gMenuSelection = HARBOUR_MASTERS_MENU; - break; - case 1: - gMenuSelection = LOGO_INTRO_MENU; - break; - case 2: - gMenuSelection = START_MENU; - break; - case 3: - gMenuSelection = MAIN_MENU; - break; - } - - // Close the editor. - if (gEditor.IsEnabled()) { - gEditor.Disable(); - } - - // Set the debug menu track browsing index back to zero - TrackBrowser::Instance->Reset(); - - // Debug mode override gSkipIntro - if (CVarGetInteger("gEnableDebugMode", 0) == true) { - gMenuSelection = START_MENU; - } else { - gMenuSelection = LOGO_INTRO_MENU; - } + // Only request the reset: it is applied at the top of the next game + // frame (ApplyPendingReset in Game.cpp), where it cannot race the menu + // state machine mid-fade or be swallowed as a repeated gamestate write. + CM_RequestReset(); } private: