Add analytics integration with Horchposten backend

Implements session-based analytics tracking that sends gameplay
stats to the Horchposten API. Adds stats.{c,h} for accumulating
per-session metrics (kills, deaths, shots, dashes, jumps, pickups,
damage, time) and analytics.{c,h} with EM_JS bridge for fetch()
calls to the backend. Client ID is persisted in localStorage.
Session start/end hooks are wired into all game lifecycle events
(level transitions, restart, quit, tab close via sendBeacon).
Analytics URL/key are configured via data attributes on the canvas
container. Non-WASM builds compile with no-op stubs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-08 17:11:39 +00:00
parent 4407932a2d
commit a23ecaf4c1
8 changed files with 331 additions and 1 deletions

View File

@@ -8,6 +8,7 @@
#include "game/spacecraft.h"
#include "game/sprites.h"
#include "game/entity_registry.h"
#include "game/stats.h"
#include "engine/core.h"
#include "engine/renderer.h"
#include "engine/physics.h"
@@ -174,9 +175,11 @@ 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(
@@ -210,6 +213,7 @@ static void damage_entity(Entity *target, int damage) {
static void damage_player(Entity *player, int damage, Entity *source) {
PlayerData *ppd = (PlayerData *)player->data;
stats_record_damage_taken(damage);
damage_entity(player, damage);
/* Screen shake on player hit (stronger) */
@@ -264,6 +268,7 @@ static void handle_collisions(EntityManager *em) {
if (from_player && entity_is_enemy(b)) {
if (physics_overlap(&a->body, &b->body)) {
damage_entity(b, a->damage);
stats_record_shot_hit();
hit = true;
}
}
@@ -366,6 +371,7 @@ static void handle_collisions(EntityManager *em) {
}
if (picked_up) {
stats_record_pickup();
/* Pickup particles */
Vec2 center = vec2(
a->body.pos.x + a->body.size.x * 0.5f,
@@ -566,6 +572,7 @@ void level_update(Level *level, float dt) {
for (int i = 0; i < level->entities.count; i++) {
Entity *e = &level->entities.entities[i];
if (e->active && e->type == ENT_PLAYER && player_wants_respawn(e)) {
stats_record_death();
player_respawn(e, level->map.player_spawn);
Vec2 center = vec2(
e->body.pos.x + e->body.size.x * 0.5f,