Offline Earnings
Idle and incremental games compute “what happened while you were gone” at boot and reward the player. Tracking that moment cleanly tells you:
- How often players come back, and after how long (return cadence)
- Whether your offline-cap is set right (
was_cappeddistribution) - Which resources actually accumulate offline vs. on-screen
- Whether long-offline players churn or convert
There is no dedicated SDK module for this — it’s a custom track() event because every idle game’s economy is different. This recipe is the pattern that’s been battle-tested in the Quest Data dogfooding game.
The Event
Section titled “The Event”Fire once at boot, after you’ve calculated offline duration and applied gains:
# scripts/idle_manager.gd (or wherever your offline calc lives)func _calculate_offline_progress(): var current_time := int(Time.get_unix_time_from_system()) var offline_seconds := current_time - player_data.last_played_time
# Apply your cap (most idle games cap offline at 8h–24h) var max_offline_seconds := BalancingManager.get_max_offline_time_seconds() var capped_seconds := min(offline_seconds, max_offline_seconds)
# Run your offline simulation var resource_gains := _simulate_offline_production(capped_seconds)
# Track it — even if offline_seconds is small. The notification UX # is a separate decision; the analytics event always fires so you # get the full distribution. QuestData.track("offline_earnings", { "offline_seconds": offline_seconds, "capped_seconds": capped_seconds, "was_capped": offline_seconds > max_offline_seconds, "resource_gain_count": resource_gains.size(), "expeditions_ready": expeditions_ready.size(), })
player_data.last_played_time = current_time save_player_data()Why These Properties
Section titled “Why These Properties”| Property | Why |
|---|---|
offline_seconds | The raw value. Build histograms: how many players return after 1h vs. 24h vs. 7d? Cohort it. |
capped_seconds | The value you actually rewarded. Difference vs. offline_seconds is gameplay-visible damage. |
was_capped | Boolean flag for fast filtering. WHERE was_capped = true instantly finds players hitting your cap. |
resource_gain_count | Diversity metric. A player whose offline earnings only touch 1 resource is mid-game; one touching 10 resources is late-game. |
| Game-specific extras | Any structural counters that move during offline (expeditions, recipes finished, …). Skip per-resource amounts — they explode the JSONB. |
Don’t Track
Section titled “Don’t Track”- Per-resource amounts (
gold_gained: 12345). One row per resource scales linearly with your economy and bloats event payloads. Track totals orgain_count. If you really want amounts, fire one event per resource with{ resource_id, amount }. true_amountvsdisplayed_amount. If you have a logarithmic economy with billions of gold, the dashboard will struggle to chart it. Uselog10(amount)as a property if you need it.- The full
resource_gainsdict as a single property. Useresource_gain_countinstead — same insight, smaller payload.
Example Queries
Section titled “Example Queries”Return-time histogram (Player Explorer → Events → filter offline_earnings):
“Distribution of
offline_secondsfor the last 7 days, bucket by hour.”
Tells you whether your push-notification cadence matches actual return behavior.
Cap pressure (Custom event chart):
“Count of
offline_earningsevents grouped bywas_cappedper day.”
If the true slice stays above ~30% your cap is too tight — players are hitting it and feeling the missing earnings. Raise it.
Churn correlation (Funnel, advanced):
“Players who fired
offline_earningswithoffline_seconds > 86400— what’s their D7 retention?”
Long-absence return events are the only signal you have for “almost-churned” players. If their retention is low, your re-engagement loop is broken.
Pair With Player Properties
Section titled “Pair With Player Properties”For long-term tracking, mirror the offline state into a Player Property so segments and Remote Config can react:
QuestData.set_user_properties({ "last_offline_seconds": offline_seconds, "lifetime_offline_count": player_data.lifetime_offline_count + 1,})Then create a segment “Long-absence returners” = last_offline_seconds > 86400 and feed them a Remote Config welcome bonus.
Pair With Tags
Section titled “Pair With Tags”Use Player Tags for boolean state:
if offline_seconds > 7 * 86400: QuestData.set_user_tag("returning_player")returning_player becomes a one-click segment in the dashboard.
How the SDK Helps
Section titled “How the SDK Helps”Nothing special — track() already does:
- Batched HTTP delivery (10 events or 10 seconds, whichever first)
- Offline queue persisted to
user://quest_queue.save - Auto-retry on network failure
- Session ID + player ID auto-attached
The “boot moment” is the trickiest timing in an idle game. Make sure your SDK is initialized before _calculate_offline_progress() runs — see the Quick Start for autoload order.
See Also
Section titled “See Also”- Event Tracking — full event API
- Players & Segments — properties and tags
- Live Balancing — tune the offline cap from the dashboard without a rebuild