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 1b5cae30..31468814 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-10 | [android_v7a_minsdk_too_high](android_v7a_minsdk_too_high_2026-07-10.md) | **FIXED (working tree)** - The `armeabi-v7a` v1.5 APK failed to install on pre-Android-8.0 devices with `INSTALL_FAILED_OLDER_SDK` because `minSdk` defaulted to 26 — a copy-pasted skeleton value, never an analyzed floor. The only real API-26 dependency was a *forced* AAudio audio-driver hint (now gated at runtime via `android_get_device_api_level()`, falling back to OpenSL ES below 26). The true 32-bit-ARM floor is API 24 (libc++ `` needs `fseeko`/`ftello`, `__INTRODUCED_IN(24)` on ILP32). `minSdk` lowered 26 → 24 (Android 8.0 → 7.0). Reproduced + verified on a real ARMv7 API-25 emulator. | | 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. | 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 09b360e3..62c17c95 100644 --- a/port/port.cpp +++ b/port/port.cpp @@ -116,6 +116,7 @@ extern "C" void* sModBridgeAnchorDataFilesRef = (void*)&dFTManagerDataFiles_Ref; #include #include +#include #ifdef __APPLE__ #include #endif @@ -590,6 +591,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[]); @@ -820,6 +846,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. //