Fix review issues in analytics integration

1. Race condition: session_end now waits for an in-flight
   session_start promise before sending, so quick restarts
   don't drop the end call.

2. beforeunload: replaced sendBeacon (which can't set headers)
   with fetch(..., keepalive: true) so the X-API-Key header
   is included and the backend doesn't 401.

3. Stats double-counting: removed stats_record_damage_dealt
   and stats_record_kill from damage_entity (which was called
   for all damage including player deaths). Now only recorded
   at player-sourced call sites (projectile hits, stomps).

4. Removed const-cast: analytics_session_end now takes
   GameStats* (non-const) since stats_update_score mutates it.

5. beforeunload now uses stashed stats from the last C-side
   session_end call instead of hardcoded zeroes. Session ID is
   cleared synchronously before async fetch to prevent races.

6. Removed unused stdint.h include from stats.h.
This commit is contained in:
2026-03-08 19:29:11 +00:00
parent a23ecaf4c1
commit 322dd184ab
5 changed files with 94 additions and 51 deletions

View File

@@ -175,11 +175,9 @@ bool level_load_generated(Level *level, Tilemap *gen_map) {
static Camera *s_active_camera = NULL;
static void damage_entity(Entity *target, int damage) {
stats_record_damage_dealt(damage);
target->health -= damage;
if (target->health <= 0) {
target->flags |= ENTITY_DEAD;
stats_record_kill();
/* Death particles — centered on entity */
Vec2 center = vec2(
@@ -215,6 +213,8 @@ static void damage_player(Entity *player, int damage, Entity *source) {
PlayerData *ppd = (PlayerData *)player->data;
stats_record_damage_taken(damage);
damage_entity(player, damage);
/* Note: damage_taken is recorded here; damage_dealt and kills are
* recorded at the specific call sites that deal player-sourced damage. */
/* Screen shake on player hit (stronger) */
if (s_active_camera) {
@@ -267,8 +267,10 @@ static void handle_collisions(EntityManager *em) {
/* Player bullet hits enemies */
if (from_player && entity_is_enemy(b)) {
if (physics_overlap(&a->body, &b->body)) {
stats_record_damage_dealt(a->damage);
damage_entity(b, a->damage);
stats_record_shot_hit();
if (b->flags & ENTITY_DEAD) stats_record_kill();
hit = true;
}
}
@@ -300,7 +302,9 @@ static void handle_collisions(EntityManager *em) {
a->body.pos.y + a->body.size.y * 0.5f);
if (stomping) {
stats_record_damage_dealt(2);
damage_entity(a, 2);
if (a->flags & ENTITY_DEAD) stats_record_kill();
player->body.vel.y = -PLAYER_JUMP_FORCE * 0.7f;
} else {
damage_player(player, a->damage, a);