Fix #28: stash live stats for beforeunload so leaderboard gets real score
All checks were successful
CI / build (pull_request) Successful in 33s

The beforeunload fallback in shell.html sends session-end data when the
user closes the tab. Previously Module._analyticsLastStats was only set
inside doEnd(), which runs when C explicitly ends a session. For the
very common case of a player closing the tab mid-game (first session),
the field was null and the fallback sent score 0.

Add analytics_stash_stats() which serialises current stats into
_analyticsLastStats every frame, so the beforeunload handler always
has up-to-date data regardless of when the tab is closed.
This commit is contained in:
2026-03-19 05:24:43 +00:00
parent c62aae16dc
commit b454c98758
3 changed files with 34 additions and 1 deletions

View File

@@ -167,6 +167,20 @@ EM_JS(void, js_analytics_send_end, (int score, int level_reached,
doEnd(sid, endReason, score, level_reached, lives_used, duration_secs); doEnd(sid, endReason, score, level_reached, lives_used, duration_secs);
}); });
/* Stash current stats as JSON so the beforeunload fallback in
* shell.html has up-to-date data if the user closes the tab. */
EM_JS(void, js_analytics_stash_stats, (int score, int level_reached,
int lives_used, int duration_secs), {
if (!Module._analyticsUrl) return;
Module._analyticsLastStats = JSON.stringify({
score: score,
level_reached: level_reached > 0 ? level_reached : 1,
lives_used: lives_used,
duration_seconds: duration_secs,
end_reason: 'quit'
});
});
/* ── C wrappers ─────────────────────────────────────────────────── */ /* ── C wrappers ─────────────────────────────────────────────────── */
void analytics_init(void) { void analytics_init(void) {
@@ -188,6 +202,16 @@ void analytics_session_end(GameStats *stats, const char *end_reason) {
); );
} }
void analytics_stash_stats(GameStats *stats) {
stats_update_score(stats);
js_analytics_stash_stats(
stats->score,
stats->levels_completed > 0 ? stats->levels_completed : 1,
stats->deaths,
(int)stats->time_elapsed
);
}
#else #else
/* ── Non-WASM stubs ─────────────────────────────────────────────── */ /* ── Non-WASM stubs ─────────────────────────────────────────────── */
@@ -202,4 +226,8 @@ void analytics_session_end(GameStats *stats, const char *end_reason) {
(void)end_reason; (void)end_reason;
} }
void analytics_stash_stats(GameStats *stats) {
(void)stats;
}
#endif /* __EMSCRIPTEN__ */ #endif /* __EMSCRIPTEN__ */

View File

@@ -15,4 +15,8 @@ void analytics_session_start(void);
* end_reason: "death", "quit", "timeout", or "completed". */ * end_reason: "death", "quit", "timeout", or "completed". */
void analytics_session_end(GameStats *stats, const char *end_reason); void analytics_session_end(GameStats *stats, const char *end_reason);
/* Stash current stats so the beforeunload fallback has up-to-date data
* if the user closes the tab mid-session. Call periodically. */
void analytics_stash_stats(GameStats *stats);
#endif /* JNR_ANALYTICS_H */ #endif /* JNR_ANALYTICS_H */

View File

@@ -578,9 +578,10 @@ static void game_update(float dt) {
level_update(&s_level, dt); level_update(&s_level, dt);
/* Accumulate play time */ /* Accumulate play time and keep the beforeunload fallback current. */
if (s_session_active) { if (s_session_active) {
s_stats.time_elapsed += dt; s_stats.time_elapsed += dt;
analytics_stash_stats(&s_stats);
} }
/* Check for level exit transition */ /* Check for level exit transition */