Fix music never playing - game speed nearly doubled (task #49)

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 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 16:12:15 +03:00
co-authored by Claude
parent 1c6e2b793a
commit e773ec4396
+49 -4
View File
@@ -1,5 +1,7 @@
#include "fmod_shims.h"
#include <cstring>
#include <mutex>
#include <set>
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<uint32_t> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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; }