From e773ec439627d70cf1e3e2cc3f42012a05496ebe Mon Sep 17 00:00:00 2001 From: megboyzz Date: Sat, 19 Sep 2026 16:12:15 +0300 Subject: [PATCH] Fix music never playing - game speed nearly doubled (task #49) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Channel::isPlaying answered FALSE for a channel System::playSound had just handed out with a success code. The guest polled it, concluded the track had finished, and started the next one - forever. Each cycle reopened the OBB asset filesystem ON THE GLThread, stalling frames directly. Found from a user observation ("треки меняются очень быстро в меню паузы"), then measured: a different track roughly every 1.5s, picked non-sequentially (DEAD SARA, DEAD SARA, GREEN DAY, SKRILLEX, GREEN DAY, CROSSES, ICONA POP, JOY FORMIDABLE), so not a walk through playlists.sb but a fresh selection each time. Now tracks the channels playSound hands out and reports those as playing until Channel::stop drops them, so normal transitions still work. A one-time log states plainly that there is no audio backend and playback is simulated, instead of leaving that only in a comment. Measured on the Pixel 6a with the race's own clock, same method both times: game time vs wall clock 7.69x -> 3.99x useAssetsFileSystem 1.00/s -> 0.00/s (in-race, fully stopped) faults 0 -> 0 27s of wall time advanced the race timer 18.82 -> 25.58 (6.76s); the two sub-intervals gave 4.18x and 3.81x. Third instance today of one failure shape: a shim reporting SUCCESS while handing back an empty or negative value. See AndroidBitmap_lockPixels (task #41) and the synthetic-swap premise (task #47). Co-Authored-By: Claude --- mpcore/src/main/cpp/emu/fmod_shims.cpp | 53 ++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/mpcore/src/main/cpp/emu/fmod_shims.cpp b/mpcore/src/main/cpp/emu/fmod_shims.cpp index 5473028..39fae87 100644 --- a/mpcore/src/main/cpp/emu/fmod_shims.cpp +++ b/mpcore/src/main/cpp/emu/fmod_shims.cpp @@ -1,5 +1,7 @@ #include "fmod_shims.h" #include +#include +#include namespace { @@ -256,9 +258,37 @@ uint32_t Shim_System_getSoftwareFormat(GuestEngine& eng, uint32_t, uint32_t out1 } uint32_t Shim_System_setSoftwareFormat(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; } uint32_t Shim_System_set3DSpeakerPosition(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; } +// FIX (2026-09-19, task #49). Channels handed out by System::playSound and +// not yet stopped. Without this the music never played: playSound reported +// success and returned a fake channel, then Channel::isPlaying answered +// FALSE for it, so the game concluded the track had finished and immediately +// started the next one - forever. Measured in the pause menu: a different +// track roughly every 1.5s, picked non-sequentially (DEAD SARA, DEAD SARA, +// GREEN DAY, SKRILLEX, GREEN DAY, CROSSES, ICONA POP, JOY FORMIDABLE), so +// not a walk through playlists.sb but a fresh selection each time. +// +// Third instance today of the same failure shape - a shim reporting SUCCESS +// while handing back an empty/negative value (see AndroidBitmap_lockPixels +// in gles_shim.cpp, task #41). Hence the log below: this engine has no audio +// backend, so playback here is simulated, and that fact belongs in the log +// rather than only in a comment. +std::set g_playingChannels; +std::mutex g_playingChannelsMutex; + uint32_t Shim_System_playSound(GuestEngine& eng, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t sp) { uint32_t out = eng.ReadIncomingArg(4, 0, 0, 0, 0, sp); // 5th total arg (this,channelid,sound,paused,+channel**) - OutPtr(eng, out, FakeHandle(eng)); + GuestAddr channel = FakeHandle(eng); + OutPtr(eng, out, channel); + { + std::lock_guard lock(g_playingChannelsMutex); + g_playingChannels.insert(channel); + } + static std::once_flag once; + std::call_once(once, [] { + Log("fmod_shims: no audio backend - System::playSound will report channels as PLAYING until " + "the guest stops them. Sound is silent by design; without this the guest sees every track " + "finish instantly and thrashes through the playlist (task #49)."); + }); return kFmodOk; } uint32_t Shim_System_setOutput(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; } @@ -266,14 +296,29 @@ uint32_t Shim_System_setOutput(GuestEngine&, uint32_t, uint32_t, uint32_t, uint3 // ---- Channel ---- uint32_t Shim_Channel_setCallback(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; } uint32_t Shim_Channel_setPriority(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; } -uint32_t Shim_Channel_stop(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; } +uint32_t Shim_Channel_stop(GuestEngine&, uint32_t r0, uint32_t, uint32_t, uint32_t, uint32_t) { + // r0 is `this`. Dropping it here is what lets the guest end a track on its + // own terms - the next isPlaying() then honestly answers false, so normal + // transitions (race ends, menu change) still work. + std::lock_guard lock(g_playingChannelsMutex); + g_playingChannels.erase(r0); + return kFmodOk; +} uint32_t Shim_Channel_setMute(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; } uint32_t Shim_Channel_getPaused(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) { OutBool(eng, out, false); return kFmodOk; } -uint32_t Shim_Channel_isPlaying(GuestEngine& eng, uint32_t, uint32_t out, uint32_t, uint32_t, uint32_t) { - OutBool(eng, out, false); +uint32_t Shim_Channel_isPlaying(GuestEngine& eng, uint32_t r0, uint32_t out, uint32_t, uint32_t, uint32_t) { + // See g_playingChannels' comment. Answering "playing" only for channels we + // actually handed out - rather than a blanket true - keeps a stale or + // never-started channel honest. + bool playing; + { + std::lock_guard lock(g_playingChannelsMutex); + playing = g_playingChannels.count(r0) != 0; + } + OutBool(eng, out, playing); return kFmodOk; } uint32_t Shim_Channel_setPaused(GuestEngine&, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) { return kFmodOk; }