Skip to content

Retention

How the platform measures, predicts, and acts on player retention. The goal is to keep players engaged once they're past first-login, recover players who drift toward inactivity, and produce honest engagement metrics for operators and region owners.

This document is a target spec — it describes how the retention machinery should work end-to-end. Inline status (✅/🚧/📐) lives in FEATURES; here we describe target state.

Engagement metrics

Daily / Weekly / Monthly Active

The standard funnel:

Metric Definition Window
DAU Distinct players with ≥ 1 logged action in the last 24 hours 24h
WAU Distinct players with ≥ 1 logged action in the last 7 days 7d
MAU Distinct players with ≥ 1 logged action in the last 30 days 30d
Stickiness DAU ÷ MAU derived

A "logged action" is any event recorded by PlayerActivityService.track_activity — login, trade, combat, sector move, dock, planet land, warp.

Online now

PlayerActivityService.get_online_player_count returns the size of the activity:online_players Redis set. Membership is added on track_login, removed on track_logout, and TTL'd if the session goes silent.

Session metrics

Per session captured in Redis under activity:session:{player_id} with TTL 24h:

{
  "login_at": "...",
  "last_activity_at": "...",
  "actions_count": int,
  "trades_count": int,
  "trade_volume": int,
  "combat_events": int,
  "sectors_visited": [...],
  "session_duration_minutes": int  (computed at logout)
}

AnalyticsService._calculate_average_session_time produces the rolling mean over a configurable window (default 7 days).

Retention rate

retention_rate(N days) =
  (players whose created_at <= now - N days AND is_active = true)
  ÷
  (players whose created_at <= now - N days)

AnalyticsService._calculate_retention_rate(days) provides 7-day and 30-day rolling rates. These are surfaced on the admin dashboard and exposed via the analytics API.

Activity tracking pipeline

[Game action]
     |
     v
[PlayerActivityService.track_activity]
     |  (stores event in Redis)
     v
[PlayerActivity table]   <-- async writeback for durable analytics
     |
     v
[AnalyticsService aggregations]
     |
     v
[Admin dashboard / region governor reports]

Redis ↔ Postgres split

  • Redis stores hot, ephemeral session and event data with TTL — fast reads, no schema migrations.
  • Postgres stores PlayerActivity, PlayerSession, PlayerAnalyticsSnapshot for durable analytics, retention math, and behavioral analysis.

Writes happen first to Redis (low latency); a writeback job (target: every 5 minutes) drains recent events into Postgres for durable storage. On Redis loss the most-recent ~5 minutes of telemetry can be lost; live gameplay is unaffected.

Inactivity decay

Several systems decay when a player goes quiet. Each is independent.

ARIA relationship

Player.aria_relationship_score decays by 1 point per day inactive (apply_inactivity_decay in the ARIA service). Never falls below 0. Reset to current value on next login. See FEATURES/gameplay/aria-companion.md.

Faction reputation

FactionService.apply_reputation_decay runs per player periodically:

  • Reputation above +100 or below −100 drifts toward neutral when last updated > 30 days ago.
  • Drift rate: 1 point/day past the 30-day threshold.
  • Maximum decay per call: 50 points.
  • Never crosses the ±100 floor (decay stops at the neutral band).
  • Skipped if decay_paused or is_locked.

Personal reputation

Weekly tick decays toward zero: - score > 0: score -= 5 (floor 0). - score < 0: score += 5 (ceiling 0).

Applied to all active players. See SYSTEMS/bounty-and-reputation.md.

Regional standing

RegionalMembership.reputation_score does not auto-decay by default. A region's owner can opt in to per-region decay rules (target spec).

Turn regeneration

Turns regenerate at a fixed rate regardless of activity (see SYSTEMS/turn-regeneration.md) — they accumulate up to a cap. Inactivity does not cost turns; it just leaves them maxed.

At-risk signals

The behavior analyzer (PlayerBehaviorAnalyzer.analyze_player_behavior) produces a profile per player including a confidence score and behavioral cluster. The retention layer reads this to derive at-risk signals:

Signal Threshold Meaning
dormant_session No login in 7+ days Player is drifting
lapsed No login in 30+ days High churn risk
declining_session_length Last 5 sessions trending down (>30% drop) Engagement waning
early_logout_streak 3 consecutive sessions < 5 minutes Frustration / boredom
negative_combat_streak Recent kill/death ratio inverted Player getting farmed
economic_loss_streak Recent net credit loss > 50% of holdings Trading struggles
social_isolation Solo player with no team / messages in 14 days Social hook missing

Signals are computed nightly; flagged players land in a re-engagement queue.

Re-engagement campaigns

The platform supports several re-engagement levers, applied based on signal severity.

Email / push reminder

For lapsed players, a templated email is queued (target spec — uses platform mail service, opt-in only). Frequency cap: at most one per 14 days.

In-game ARIA welcome-back

When a player returns after dormant_session, ARIA's first dialogue references the gap explicitly and summarises what changed:

"Welcome back, Captain. It's been 12 days. Two new sectors opened in your home cluster, and Equipment prices at Trade Hub Beta are at a 30-day low. Want a route?"

This is gated on aria_consciousness_level ≥ 2 (enough memories to summarise meaningfully).

Returning-player turn bonus

Player.turns is topped up by a "welcome back" bonus if last login was > 7 days ago. Bonus: min(500, days_inactive × 50). Capped to prevent abuse from alt accounts.

Daily-quest seed

The daily-quest system (see FEATURES/gameplay/missions.md) seeds at-risk players with easier quests on return — explicit "first day back" tier. Avoids overwhelming with hard objectives.

Seasonal events

Quarterly seasonal events (Galactic Trade Festival, Frontier Days, Founders' Anniversary) provide: - Time-limited missions with unique rewards. - Increased reputation gain rates with all factions. - Special cosmetic rewards tied to login frequency during the event. - Region-wide buffs (decreased tax, free turn regeneration boost).

Events are scheduled on a fixed calendar; entry conditions are simple ("be logged in during the event window"). Rewards scale with consecutive-day participation.

Region-owner retention metrics

Region owners (see OPERATIONS/multi-regional.md) get a dashboard with their region's active_players_30d, total_trade_volume, and a simplified retention curve. This drives the regional governance feedback loop — owners who can't keep players engaged see it directly.

Region.active_players_30d is recomputed nightly from the underlying activity data.

Privacy

Activity tracking is OWASP-compliant: - All event data is keyed by player_id, with explicit access controls. - Players can request a data export of their activity history. - Players can opt out of analytics (engagement events still need to fire for game state, but they are not aggregated for retention or behavioral analysis). - IP addresses and user-agent strings are stored only for security logs (ARIASecurityLog), not retention analytics. - Data retention: raw events 90 days, aggregated snapshots indefinitely (with no PII).

Source map

Concern Path (target)
Activity tracking (Redis layer) services/gameserver/src/services/player_activity_service.py
Behavior analysis (ML clustering, anomaly) services/gameserver/src/services/player_behavior_analyzer.py
Aggregate analytics services/gameserver/src/services/analytics_service.py
Models services/gameserver/src/models/player_analytics.py (PlayerSession, PlayerActivity, PlayerAnalyticsSnapshot)
ARIA inactivity decay services/gameserver/src/services/aria_personal_intelligence_service.py:apply_inactivity_decay
Faction decay services/gameserver/src/services/faction_service.py:apply_reputation_decay
Personal reputation decay services/gameserver/src/services/personal_reputation_service.py:apply_weekly_decay
Region 30d active recompute services/gameserver/src/services/regional_governance_service.py
Re-engagement scheduler services/gameserver/src/services/retention_scheduler.py (target — not yet split out)