Skip to content
Merged
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
2 changes: 1 addition & 1 deletion decomp
1 change: 1 addition & 0 deletions docs/bugs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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++ `<fstream>` 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. |
Expand Down
23 changes: 23 additions & 0 deletions docs/bugs/reset_command_unregistered_2026-07-12.md
Original file line number Diff line number Diff line change
@@ -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.
30 changes: 30 additions & 0 deletions port/port.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ extern "C" void* sModBridgeAnchorDataFilesRef = (void*)&dFTManagerDataFiles_Ref;
#include <filesystem>
#include <system_error>

#include <ship/debug/Console.h>
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#endif
Expand Down Expand Up @@ -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<Ship::Console> console, std::vector<std::string> args,
std::string* output) {
portSCManagerRequestReset();
if (output) {
*output = "Resetting to the opening scene...";
}
return 0;
}

extern "C" {

static int PortInitImpl(int argc, char* argv[]);
Expand Down Expand Up @@ -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.
//
Expand Down
Loading