From b454c98758c3d661cce5532cf552063444a61165 Mon Sep 17 00:00:00 2001 From: Le Serjant Date: Thu, 19 Mar 2026 05:24:43 +0000 Subject: [PATCH] Fix #28: stash live stats for beforeunload so leaderboard gets real score 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. --- src/game/analytics.c | 28 ++++++++++++++++++++++++++++ src/game/analytics.h | 4 ++++ src/main.c | 3 ++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/game/analytics.c b/src/game/analytics.c index 7b40b33..74057b1 100644 --- a/src/game/analytics.c +++ b/src/game/analytics.c @@ -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); }); +/* 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 ─────────────────────────────────────────────────── */ 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 /* ── Non-WASM stubs ─────────────────────────────────────────────── */ @@ -202,4 +226,8 @@ void analytics_session_end(GameStats *stats, const char *end_reason) { (void)end_reason; } +void analytics_stash_stats(GameStats *stats) { + (void)stats; +} + #endif /* __EMSCRIPTEN__ */ diff --git a/src/game/analytics.h b/src/game/analytics.h index cc55822..df8b12b 100644 --- a/src/game/analytics.h +++ b/src/game/analytics.h @@ -15,4 +15,8 @@ void analytics_session_start(void); * end_reason: "death", "quit", "timeout", or "completed". */ 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 */ diff --git a/src/main.c b/src/main.c index a718758..ac45873 100644 --- a/src/main.c +++ b/src/main.c @@ -578,9 +578,10 @@ static void game_update(float dt) { level_update(&s_level, dt); - /* Accumulate play time */ + /* Accumulate play time and keep the beforeunload fallback current. */ if (s_session_active) { s_stats.time_elapsed += dt; + analytics_stash_stats(&s_stats); } /* Check for level exit transition */