From 4a108b0712530a8f2a842d254ac36ccfc1b36046 Mon Sep 17 00:00:00 2001 From: siliconports Date: Sun, 12 Jul 2026 19:13:14 -0700 Subject: [PATCH] Fix the Reset button / Ctrl+Cmd-R: register the missing reset command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both the ESC-menu Reset button and the keyboard shortcut Dispatch a "reset" console command that was never registered, so they printed "[LUS] Command not found" and did nothing. Register it and have it perform an in-game reset to the boot scene (VS CSS under Boot-to-CSS) via portSCManagerRequestReset() in the decomp's scene manager. Bump decomp: sticky reset target (the 1P sub-scene manager rewrites scene_curr between its chained sub-scenes; a mid-match reset crashed in ftManagerSetupFilesAllKind before the per-sub-scene bail guards) and the native is_reset abort flag (without it a mid-VS-match reset was read as "time up, tied score" and chained into Sudden Death). Full write-up in docs/bugs/reset_command_unregistered_2026-07-12.md. Playtested: reset from title, menus, mid-VS-match, mid-1P-match, and a 1P bonus stage — all land at the opening scene, no crashes. Co-Authored-By: Claude Fable 5 --- decomp | 2 +- docs/bugs/README.md | 1 + .../reset_command_unregistered_2026-07-12.md | 23 ++++++++++++++ port/port.cpp | 31 +++++++++++++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 docs/bugs/reset_command_unregistered_2026-07-12.md diff --git a/decomp b/decomp index 25f9a8c9..dbc69f44 160000 --- a/decomp +++ b/decomp @@ -1 +1 @@ -Subproject commit 25f9a8c9e9ed0923c06d93f09109e8ddfbcd7013 +Subproject commit dbc69f44d9c353d61a09735d79192bdf98b146b0 diff --git a/docs/bugs/README.md b/docs/bugs/README.md index 3670c9c9..09aaca7d 100644 --- a/docs/bugs/README.md +++ b/docs/bugs/README.md @@ -6,6 +6,7 @@ This directory documents significant bugs encountered during the port, their sym | Date | Slug | Summary | |------|------|---------| +| 2026-07-12 | [reset_command_unregistered](reset_command_unregistered_2026-07-12.md) | **FIXED (port + decomp PORT blocks)** — The ESC-menu Reset button and Ctrl/Cmd-R did nothing: both `Dispatch("reset")` at the LUS console but no `reset` command was ever registered. Fixed in three layers: the port registers `reset` → `portSCManagerRequestReset()` (scmanager.c) requests an in-game return to the boot scene via the normal scene-exit path; the reset is STICKY because `sc1PManagerUpdateScene` chains 1P sub-scenes and rewrites `scene_curr` itself (a mid-1P-match reset crashed in `ftManagerSetupFilesAllKind` before `SC1P_PORT_RESET_BAIL()` guards were added after all 12 sub-scene calls); and it sets the game's native `is_reset` abort flag (the A+B+R+Z combo's) so battle-end interpreters cooperate — without it a VS reset chained into Sudden Death. Grep-audit: `scmanager.c` + `sc1pmanager.c` are the only scene-chaining files. | | 2026-07-06 | [android_v7a_release_artifact](android_v7a_release_artifact_2026-07-06.md) | **FIXED (release packaging)** - The `armeabi-v7a` Android build existed only as a standalone debug validation workflow, so tag releases uploaded only the default `arm64-v8a` `BattleShip-android.apk`. Release CI now builds Android through an ABI matrix, keeps the historical arm64 filename, uploads `BattleShip-android-armeabi-v7a.apk` for 32-bit devices, and publishes both APKs in the draft release. | | 2026-07-06 | [macos_sdl2compat_sdl3_dlopen](macos_sdl2compat_sdl3_dlopen_2026-07-06.md) | **FIXED (release packaging)** - macOS v1.5 could launch to "Failed loading SDL3 library" because Homebrew's current `sdl2` can resolve to `sdl2-compat`, which opens SDL3 via `dlopen()` rather than a Mach-O load command. `dylibbundler` bundled `libSDL2-2.0.0.dylib` but missed `libSDL3.dylib`. The package script now detects sdl2-compat markers, copies/signs `libSDL3.0.dylib` into `Contents/Frameworks`, and creates the `libSDL3.dylib` symlink it probes. | | 2026-07-06 | [macos_dmg_styled_release](macos_dmg_styled_release_2026-07-06.md) | **FIXED (release packaging)** - v1.4+ macOS release DMGs lost the styled `create-dmg` Finder window because `025c8b86` made CI prefer a plain `hdiutil` image after GitHub's macOS runner image hung in Finder/AppleScript styling for 30+ minutes. Release CI now explicitly sets `DMG_STYLED=1`, `DMG_REQUIRE_STYLED=1`, and `DMG_TIMEOUT=300`, so the polished banner/large-icon drag-to-Applications installer is required while still timing out instead of hanging indefinitely. | diff --git a/docs/bugs/reset_command_unregistered_2026-07-12.md b/docs/bugs/reset_command_unregistered_2026-07-12.md new file mode 100644 index 00000000..12b73e46 --- /dev/null +++ b/docs/bugs/reset_command_unregistered_2026-07-12.md @@ -0,0 +1,23 @@ +# Reset button / Ctrl+Cmd-R did nothing — "reset" console command never registered + +**Status:** FIXED (port + decomp PORT blocks) + +**Symptoms:** The ESC-menu Reset button (red ↺) and the Ctrl+R / Cmd-R shortcut did nothing in any scene. With the console open, pressing either printed `> reset` followed by `[LUS] Command not found`. + +**Root cause (three layers, found in sequence):** + +1. **Command never registered.** `port/gui/Menu.cpp` and libultraship's `Gui.cpp` both `Dispatch("reset")` at the LUS console, but the only commands ever registered were LUS's built-ins (`set/get/help/clear/unbind/bind/bind-toggle`). The dispatch fell through to "Command not found". (SpaghettiKart, the pattern source, has the same dead console dispatch — its Reset *button* works because it bypasses the console and calls a `ProcessReset()` that writes MK64's global `gGamestateNext` state machine. SSB64 has no equivalent per-frame gamestate machine, so an equivalent needed real wiring.) + +2. **1P mode's nested scene chain.** First implementation set `gSCManagerSceneData.scene_curr` to the boot scene and called `syTaskmanSetLoadScene()` — the same thing every scene's own exit path does. That works for top-level scenes, but `sc1PManagerUpdateScene` chains 1P sub-scenes back-to-back and rewrites `scene_curr` itself between them. A mid-match reset in 1P Game ended the battle task, then the manager chained into the next stage intro against half-torn-down battle state → SIGSEGV in `ftManagerSetupFilesAllKind` (NULL + 0x30). Grep-audit result: `scmanager.c` and `sc1pmanager.c` are the ONLY two files in the tree that chain scenes via `*StartScene()`. + +3. **Battle-end interpretation.** A VS-match reset skipped to Sudden Death: `scVSBattleSetScoreCheckSuddenDeath` read the aborted match as "time expired, tied score". The game already has native abort semantics for this — `gSCManagerSceneData.is_reset`, set by the original A+B+R+Z combo (`ifcommon.c`), honored by `scvsbattle.c` (skips sudden death), the VS results screen, and the 1P bonus/challenger flows, and cleared at every battle start. + +**Fix:** + +- `port/port.cpp` registers `reset` at console init; the handler calls `portSCManagerRequestReset()` (decomp-side, `#ifdef PORT` — port C++ can't include decomp headers, and mirroring `SCCommonData`'s layout in the port would invite exactly the layout-drift class documented in `debug_ido_bitfield_layout.md`). +- `scmanager.c portSCManagerRequestReset()`: latches a STICKY pending flag + target scene (boot scene `nSCKindOpeningRoom`, or the VS CSS when Boot-to-CSS is active — mirroring the port's boot flow), sets the native `is_reset` flag, runs the same audio wind-down every scene exit uses (`func_800266A0_272A0`), writes `scene_prev/curr`, and calls `syTaskmanSetLoadScene()`. +- `scmanager.c portSCManagerConsumeReset()`: if pending, re-applies the target (undoing any nested-manager `scene_curr` clobber), clears the flag, returns TRUE. Consumed at the top of `scManagerRunLoop`'s dispatch (top-level case + stale-flag guarantee) and by `sc1pmanager.c`'s `SC1P_PORT_RESET_BAIL()` after **every** of its 12 sub-scene calls, which unwinds straight back to `scManagerRunLoop` (whose existing scene-boundary GObj sweep then runs as for any transition). + +Playtested: reset from title, menus, mid-VS-match (no sudden death), mid-1P-match (no crash), 1P bonus stage; all land at the opening scene with audio stopped. + +**Audit hook:** Anything that force-ends a scene must (a) survive the two scene-chaining managers (`scmanager.c`, `sc1pmanager.c` — re-grep `StartScene();` if more appear) and (b) speak `is_reset`, the game's own match-abort flag, so battle-end interpreters don't misread the abort. A scene_curr write alone is not a transition request. diff --git a/port/port.cpp b/port/port.cpp index 34a1da2e..5e6bd668 100644 --- a/port/port.cpp +++ b/port/port.cpp @@ -113,6 +113,8 @@ extern "C" void* sModBridgeAnchorDataFilesRef = (void*)&dFTManagerDataFiles_Ref; #include #include +#include + #ifdef _WIN32 #include #include @@ -583,6 +585,31 @@ static std::string PortLocateFile(const std::string& basename) { return "./" + basename; } +/* ── Console "reset" command ───────────────────────────────────────────── + * The ESC-menu Reset button (port/gui/Menu.cpp) and the Ctrl/Cmd-R shortcut + * (libultraship Gui.cpp) both Dispatch("reset") at the LUS console, but the + * command was never registered, so both fell through to "[LUS] Command not + * found" and did nothing. + * + * The handler performs an in-game reset back to the boot scene — the same + * "return to title" semantics as a console reset — via the scene manager's + * normal transition path. The mechanics live decomp-side in + * portSCManagerRequestReset() (decomp/src/sc/scmanager.c) because the port + * layer can't include decomp headers (the C shim stdlib shadows libc++ — + * see the include-path note in CMakeLists.txt) and mirroring the scene + * struct layout here would invite exactly the layout-drift bugs + * docs/debug_ido_bitfield_layout.md warns about. */ +extern "C" void portSCManagerRequestReset(void); + +static int32_t ResetCommandHandler(std::shared_ptr console, std::vector args, + std::string* output) { + portSCManagerRequestReset(); + if (output) { + *output = "Resetting to the opening scene..."; + } + return 0; +} + extern "C" { static int PortInitImpl(int argc, char* argv[]); @@ -813,6 +840,10 @@ static int PortInitImpl(int argc, char* argv[]) { if (!sContext->InitConsole()) { port_log("SSB64: InitConsole failed\n"); return 1; } port_log("SSB64: CrashHandler + Console OK\n"); + /* Back the ESC-menu Reset button / Ctrl+Cmd-R Dispatch("reset") with a + * real command — see the reset block above PortInit for the mechanism. */ + sContext->GetConsole()->AddCommand("reset", { ResetCommandHandler, "Resets the game to the opening scene" }); + // ControlDeck MUST be initialized before Window — the DXGI window proc // calls ControllerUnblockGameInput on WM_SETFOCUS during window creation. //