Skip to content

Player Activity Tracking

The player-activity layer records session and gameplay telemetry in Redis with TTL-based expiration. It adds no database tables: it leans on Redis for hot, ephemeral data and on the existing Player.last_game_login column for the one durable signal it needs.

Implemented in services/gameserver/src/services/player_activity_service.py (PlayerActivityService), reached through the module-level singleton accessor get_player_activity_service(). Each method lazily resolves the shared RedisService.

What is tracked

Two kinds of writes flow through the service:

  • Session lifecycletrack_login opens a session; track_logout finalises it, computing duration_seconds and folding the session into the rolling and daily summaries. Login and logout are wired into the auth routes (services/gameserver/src/api/routes/auth.py), mapping the authenticated user to its Player row; admins and non-player users are skipped, and any tracking failure is swallowed so it can never break auth.
  • Gameplay eventstrack_activity(player_id, event_type, details) increments the live session counters and appends an event record. The recognised event types (ActivityEventType) are login, logout, trade_buy, trade_sell, combat_attack, combat_defend, sector_move, dock, undock, planet_land, and warp. Trade events add to trades_count and trade_volume (from details["total_value"]); combat events add to combat_events; sector_move appends to a deduplicated sectors_visited list capped at the last 100 entries.

Every write also pushes an individual event onto the player's event list (_record_event), trimmed to the most recent 500 entries.

Redis keys and TTLs

Key Contents TTL
activity:session:{player_id} Current session: login/last-activity timestamps and live counters 24 hours
activity:events:{player_id} Most recent events (list, capped at 500) 7 days
activity:summary:{player_id} Rolling per-player summary: total sessions, playtime, actions, trades, trade volume, combat events, unique sectors 30 days
activity:daily:{player_id}:{date} Per-day aggregates (UTC date) 14 days
activity:online_players Set of currently-online player IDs no TTL (managed by add on login / remove on logout)

The session key is added to activity:online_players on login and removed on logout; get_online_player_count and get_online_player_ids read that set. The session key itself is deleted on logout.

Persistence is Redis-only

All activity data lives in Redis and is not durable. Counters and summaries are written with SETEX (RedisService.cache_set), so they expire on the TTLs above and are lost on a Redis flush or data loss. There is no write-back into Postgres from this service. The only database touch is the optional refresh of Player.last_game_login inside track_login, which the auth wiring does not currently pass a session to.

Consequences:

  • The online-player set, session counters, summaries, and per-day aggregates reset to zero on Redis loss.
  • The longest-lived record is the 30-day rolling summary; per-day aggregates persist 14 days, raw events 7 days, and the live session 24 hours.
  • Read accessors (get_player_session, get_player_summary, get_recent_events, get_daily_stats) return zeroed defaults when the corresponding key has expired or never existed, rather than erroring.

Source map

Concern Path
Activity service services/gameserver/src/services/player_activity_service.py
Redis cache primitives (cache_set/cache_get/cache_delete) services/gameserver/src/services/redis_service.py
Login / logout wiring services/gameserver/src/api/routes/auth.py
Durable last-login column services/gameserver/src/models/player.py (Player.last_game_login)