In-game economy

An economy event in AgentHog is an ordinary custom event with three conventional props. You declare which events source (add) or sink (remove) which currency, at a cost of one command per event, and from then on the economy is a report: per-currency sourced / sunk / net flow with supply growth, a paid-versus-earned split, ranked sources and sinks, breakdowns by any dimension, a daily series, and a health line. Virtual currency is distinct from real money, which is Revenue. The two meet exactly once, at the paid/earned split below.

Mappings are retroactive and reversible. The economy is computed from props already sitting in your events, so mapping today backfills every grant and spend you have ever sent. Nothing is re-ingested, no data is rewritten, no SDK method exists to migrate to, and ah economy unmap reverses any mapping. Instrument first, map whenever.

1. Send the events: three props

The convention is three props on any event that moves currency. Names are yours; the props are the contract:

propmeaningrule
currencywhich denomination moved: gems, coins, energy. Your names, not ISO codes.currencies exist because events name them; nothing is declared anywhere
amounthow many units movedalways positive. Direction comes from the mapping (source or sink), never from the sign. A negative amount is counted as a sign disagreement in the health line, not silently flipped or dropped
balancethe player's holding of that currency after this transactionoptional but recommended: it powers drift detection, the check that finds the source or sink you forgot to map
// Unity, but the shape is the same from any SDK or a server-side relay
AgentHog.Capture("gems_purchased", new() {
  ["currency"] = "gems", ["amount"] = 500, ["balance"] = 650,
  ["cents"] = 499, ["pack"] = "starter" });          // real-money props ride the same event

AgentHog.Capture("level_reward", new() { ["currency"] = "coins", ["amount"] = 1200, ["balance"] = 8400, ["level"] = 12 });
AgentHog.Capture("item_bought",  new() { ["currency"] = "gems",  ["amount"] = 250,  ["balance"] = 400, ["item"] = "sword_epic" });

Aggregate rapid pickups

Send one event when the run ends, not one per coin. A player vacuuming 300 coins across a level should produce a single level_reward with amount: 300. The totals are identical, and your event volume (and bill) stays proportional to gameplay moments instead of frame-rate. The same goes for continuous sinks: batch an energy tick into one energy_spent per meaningful unit.

2. Map each event: one flag

Start by letting AgentHog propose the config. ah economy suggest scans the events you already send for economy-shaped props and prints ready-to-run commands, with the share of occurrences whose amount actually parses and every flow direction labelled as the guess it is.

ah economy suggest           # propose mappings from events already sent
ah economy suggest --apply   # write every new proposal

A game following the prop convention maps each event with a single flag:

ah economy map --event gems_purchased --source
ah economy map --event item_bought   --sink

Defaults resolve when you run the command and are stored explicitly, so ah economy config always shows exactly what the numbers were computed from: every mapping fully resolved, with the command that recreates it. Unusual prop names never require renaming anything in the game; override per mapping:

flagwhat it does
--amount <prop>the prop holding the amount (default amount)
--currency-prop <prop>read the currency name per event (default: the currency prop). A typo'd value surfaces as its own report block; gemz next to gems is how you find it
--currency <name>pin a fixed name for events that carry no currency prop at all; alongside --currency-prop it is the per-row fallback
--balance <prop>the prop holding the post-transaction balance (default balance)
--paid / --earnedoverride the paid/earned classification (see the two IAP shapes below)
--match prop=valueonly count events carrying that prop value, e.g. --match mode=live. An absent prop counts as a match, so history predating the prop is not silently dropped

Bundles: one amount prop per currency

A starter pack that grants two currencies stays one event: carry one amount prop per currency and map each prop. Mappings are keyed by (event, amount prop), so both coexist, and unmap removes exactly one of them:

AgentHog.Capture("starter_pack", new() {
  ["gems_amount"] = 100, ["coins_amount"] = 5000, ["cents"] = 499 });
ah economy map --event starter_pack --amount gems_amount  --currency gems  --source
ah economy map --event starter_pack --amount coins_amount --currency coins --source

Sourced currency splits into paid (bought with real money) and earned (granted by gameplay). Which side a source lands on is config, and the common case needs none:

Atomic IAP: dollars and currency on one event. The purchase event carries both the real-money prop and the currency props, and you map it twice, once as revenue and once as an economy source. The economy side then counts as paid automatically, because its event also carries a revenue mapping:

ah revenue map gems_purchased --amount cents --unit minor --currency USD
ah economy map --event gems_purchased --source     # paid: inferred, no flag needed

Deferred grant: purchase now, claim later. The dollars land on one event and the currency lands on another (a claim screen, a delivered gift). Map the purchase event as revenue, and mark the claim event paid explicitly:

ah revenue map gem_pack_purchased --amount cents --unit minor --currency USD
ah economy map --event reward_claimed --source --paid

--paid is declared once, in config, never per call. The mirror override exists too: --earned forces a source to count as earned even when its event carries a revenue mapping.

3. Read it

ah economy                      # one block per currency
ah economy --by platform        # segment the flows
ah economy --by flag:econ_ab    # economy per A/B variant (live-ops tuning)
ah economy --series             # sourced/sunk/net per day
ah economy config               # every mapping, fully resolved
ECONOMY — finalgame · 7d · gems
sourced   1,100    paid 91% · earned 9%
sunk        250
net flow   +850    supply +85%

TOP SOURCES
event           amount  events  players
--------------  ------  ------  -------
gems_purchased   1,000       2        2
level_reward       100       1        1

TOP SINKS
event        amount  events  players
-----------  ------  ------  -------
item_bought     250       2        2

HEALTH  unparseable: 1 · sign disagreements: 1 · balance drift >5%: 66.7% of players (balance on 3 of 3 players)
numberdefinition
sourcedunits of this currency added, with its paid/earned split
sunkunits removed
net flowsourced − sunk. Positive means the supply is inflating; supply % is net over the cumulative net of all history before the window
TOP SOURCES / SINKSevery mapped event that moved this currency, ranked by amount, with event and player counts

Amounts are never summed across currencies: a two-currency game gets two blocks, because 1,000 gems plus 5,000 coins is not a number. Breakdowns follow the same attribution rule as revenue. A dimension the event itself carries (props.<key>) is read from the event and never inherited, while a person-level dimension (platform, country, flag:<key>) falls back to the player's own value, which is what makes a server-granted reward attributable to a platform or an experiment variant at all.

Every verb on this page takes --json, and the output is identical to the HTTP API payload. ah economy --help carries the full flag list.

The health line

Instrumentation gaps skew an economy silently, so the report always ends with a health line. Every figure is a gap made loud, never a correction applied:

figurewhat it means
unparseablemapped events whose amount prop was missing or not a number. Counted and excluded from totals, never silently zeroed.
sign disagreementsa negative amount on a mapped event. Amounts are sent positive; the row still counts (as its absolute value, on the mapped side), and this counter tells you the game is encoding direction in the sign.
balance driftthe share of balance-reporting players whose latest reported balance disagrees with their summed mapped flows by more than 5%. Drift means you forgot to map a source or sink; run ah economy suggest again. Balances are what the game reports, by design: AgentHog never derives them server-side, so drift stays a trustworthy signal instead of being reconciled away. Players who report no balance are excluded from the denominator, and the coverage is printed alongside. Unmeasured is never shown as 0% drift.

Suggested event names

Conventional, unenforced: adopt these names and reports populate, and ah economy suggest reads the flow direction straight from almost every one of them, because the name carries a word its guesser recognizes (a bundle like starter_pack is instead guessed from the real-money prop it usually carries). Ignore them and nothing breaks; the mapping is what matters.

eventflownote
gems_purchasedsourcehard currency bought with real money; map the same event as revenue too and the grant counts as paid
reward_claimedsourcedeferred grant landing after a purchase-now/claim-later flow; map with --paid
currency_grantedsourcea server-side grant (support comp, promo, migration)
level_rewardsourceend-of-level payout; aggregate the pickups into this one event
quest_rewardsourcequest or mission completion payout
daily_bonussourcelogin streak / calendar bonus
achievement_rewardsourceone-time achievement payout
chest_openedsourceloot container yield
starter_packsourcebundle: one amount prop PER currency (gems_amount, coins_amount), one mapping each
item_boughtsinkshop purchase paid in virtual currency
upgrade_boughtsinkupgrade or unlock paid in virtual currency
crafting_spentsinkcrafting / forging cost
energy_spentsinkconsumable spend (energy, stamina, tickets)
timer_skippedsinkhard-currency spend to skip a wait

What this does not do

  • No exchange rates between currencies. Flows stay per currency; converting gems to coins is a game-design question, not an analytics one.
  • No items or inventory. Economy tracks fungible currencies. A sword is not 250 gems; the item_bought sink records what you paid, not what you got.
  • No server-derived balances. The balance prop is the game's own report, and drift against summed flows is a first-class signal. An analytics tool that "fixes" the discrepancy would be hiding your missing mapping.

Next

Real-money purchases belong on the Revenue & LTV page; server-side grants ride the server-to-server relay; Unity setup is in the Unity guide.