Player wallets & billing
Players who run player code are first-class billing customers. Each player has one platform-scoped wallet that funds their grid compute across every org and app they play in. Player money is entirely out-of-band from org billing: a player's runaway ticker, empty wallet, or refund never appears on an org's bill and never trips the org's runtime gate.
Server-side player compute, player automations, and player compiles are metered per player — attributed to the grid owner the code executed as, never its author. Client-side execution runs on the player's own hardware and is not billed (client compiles are metered; they consume platform CPU).
The wallet
All wallet operations are viewer-scoped — a caller manages only their own wallet, with no org permission involved:
playerWalletBalance— the caller's wallet, created empty on first access.playerWalletTransactions— the ledger: top-ups, hourly usage debits, auto-recharges, refunds, and adjustments. Historicalpurchase/payout_creditrows may still appear; paid marketplace sales are off the public API.createCheckoutwith purposePLAYER_WALLET_TOPUP— fund the wallet through the ordinary hosted checkout (Stripe/PayPal). OnlyamountCentsis required. The wallet pays player compute, not store listings.beginPlayerCardSetup— vault a card on the wallet (a Stripe SetupIntent the browser confirms), enabling auto-recharge.playerAutoBilling/setPlayerAutoBilling— off-session auto-recharge from a vaulted card, with a per-period ceiling. The player gate tries an auto-recharge before ever denying for funds.
Hourly usage billing
Player usage bills on closed clock hours, exactly like org shared-usage
billing: minute counters ship from the game runtime, the biller prices the
hour's usage above the monthly trial budget at the player rate card,
and debits the wallet once per (player, app, hour) — the charge ledger is
idempotent.
Each posted charge (playerUsageCharges) splits two components the player
always sees separately:
platformCents— the platform base price (same compute-unit formula and fuel-divisor lineage as studio modules).markupCents— the studio's configured markup for that app, if any.
The per-metric snapshot on the charge records used/free/billable quantities for compute units, automation units, egress, storage and compile dimensions.
The free trial
Every player gets a monthly trial budget of 250,000 compute units in each app — a pooled allowance covering module compute and automation together, reset on the first of each UTC month. Trying player code never requires funding a wallet first: usage inside the trial charges nothing and keeps an empty wallet fully active.
There is no hourly free allowance. Any recurring hourly figure would make a small mod free forever, which is not what a trial is for; a monthly budget lets a player experiment freely and asks sustained play to pay. As a rough guide, 250,000 units is about 14 hours of a light mod running at the default 5 Hz tick, or about an hour of a database-heavy one. Those are hours of play, not wall-clock hours: a mod only ticks while the player's app has somebody in it, so a trial is not consumed while nobody is playing.
Charges below one cent are carried forward rather than rounded up, so a player running something tiny is billed what they actually used over the month instead of a rounded-up cent every hour.
Spend caps and the player gate
The effective limit on a player's spend is
min(developer policy, player self-cap, wallet balance):
playerSpendCaps/setPlayerSpendCap— self-set daily/monthly ceilings, globally or per app. Hitting a cap denies withPLAYER_SPEND_CAP.- The player gate (
playerRuntimeStates) mirrors the app runtime gate at(player, app)scope:active,grace(a one-evaluation warning), ordeniedwith a typed reason. An exhausted wallet past grace denies withPLAYER_WALLET_EMPTY.
A non-active gate pauses that player's mods only — their session and ordinary play are untouched, and no other player or the org is affected. The gate state replica-syncs to the game runtime, where the scheduler drains the player's modules within one pass and resumes them when the gate clears.
While the gate is not active, playerComputeInvoke refuses with a typed fault
the player can act on: WALLET_EMPTY (blame BUDGET, not retryable — top up
or enable auto-recharge) or SPEND_CAP_REACHED (raise or clear the cap). A
refusal for a module that is disabled, not compiled, or not the caller's own
grid is NOT_ALLOWED; a per-hour or per-day compute quota is BUDGET_EXCEEDED
and returns on the next window. Only a failure that is ours — a module that
would not load — is reported as PLATFORM_ERROR. See
Error codes for the full table.
Auto-recharge honours the threshold you set: with billable usage in the last
two hours and a balance at or below lowWaterThresholdCents, the saved card is
charged rechargeAmountCents before the balance can reach zero. Sub-cent usage
is carried forward in micro-cents and charged once it reaches a whole cent, and
a refund of a top-up (refund) or a card dispute (adjustment) leaves the
wallet the same way it arrived.
Studio configuration and visibility
Studio-facing controls (org permissions in parentheses):
| Surface | Purpose |
|---|---|
playerWasmPolicies / setPlayerWasmPolicy / deletePlayerWasmPolicy (manage_compute) | Per-player/cohort clamps at app_default, tier, grid, or user scope: module counts, tick/fuel/memory/egress budgets, unitsPerHour/unitsPerDay quotas, maxCompilesPerHour, container-create caps |
playerRateMarkup / setPlayerRateMarkup (view_billing / manage_billing) | Markup in basis points on the platform base price — the studio's usage-revenue stream, always shown to players as a separate component |
appPlayerUsage (view_compute_diagnostics) | Per-player usage aggregate: top spenders, quota utilization, compiles, cents charged |
appPlayerMarkupAccrued (view_billing) | Total markup income earned. Each charge's markup is credited to the organization wallet in the same transaction as the player's debit, and appears in the org ledger as markup_payout — so this total and the money in the wallet cannot drift apart |
playerComputeSetSwitch / playerComputeSwitches (Game API) | The kill ladder: immediate stop at player/grid/app scope, quota state retained |
Management is authoritative for the player policy table; changes replica-sync to the game runtime, where every knob is additionally clamped by the app's studio compute policy — a player policy can only tighten.