@indiegems/gem-web-sdk 23.2.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,32 +1,37 @@
1
- # Gems, Coins and Achievement Points
2
-
3
- The arcade runs three things across every game, and a player should be able to tell them apart at
4
- a glance:
5
-
6
- > **Gems = progress. Coins = what play earns: cosmetics, and play items too where the platform allows. Achievement Points = prestige.**
7
-
8
- You do not have to use any of them. A game with no platform currency and no achievements is a
9
- complete game, and the currencies your core loop runs on — gold, XP, energy, materials, cards — are
10
- yours to design however you like ([inventory-and-catalogs.md](inventory-and-catalogs.md)). Read this
11
- page before you design a reward a player carries out of your game, a price they pay with something
12
- earned elsewhere, or an achievement. The rules below decide which of the three a reward can be, and
13
- most of them are enforced at upload, so a design that ignores them is refused rather than shipped.
14
-
15
- ## 1. The three, side by side
16
-
17
- | | **Gems** (`GEMS`) | **Coins** (`ARCADE_TICKETS`) | **Achievement Points** |
18
- | ------------------ | ------------------------------------------------------ | ----------------------------------------------- | --------------------------------------------------------- |
19
- | What it is | the arcade's progression currency | the arcade's everyday currency, earned by play | permanent prestige: the **Achievement Score** |
20
- | Which games pay it | any game with a server or a serverless function | any game with a server or a serverless function | an achievement, from any game with a server or a function |
21
- | What it buys | anything a game prices in it | cosmetics; play items too, as its spend scope allows (§5) | nothing — it is never spent |
22
- | Sold for money | by the arcade's own store only, never by a game | never | never |
23
- | A Boost raises it | yes — the grants and the caps | yes — the grants and the caps | never |
24
-
25
- A player earns Gems across the arcade's games and spends them on progress anywhere: upgrades, unlocks,
26
- extra attempts, resources, characters, cards. A player earns Arcade Coins by playing almost anything and
27
- spends them on how they look: skins, outfits, visual effects, decorations, emotes, profile
28
- cosmetics, and since 25 Oct 2026 on play items too, wherever the platform's spend scope for Coins
29
- admits them (§5). Achievement points are what a player has done, summed across every game into the
1
+ # Coins and Achievement Points
2
+
3
+ The arcade runs two things across every game, and a player should be able to tell them apart at a
4
+ glance:
5
+
6
+ > **Coins = the arcade's one currency: play earns them, the Arcade sells them, games price in them. Achievement Points = prestige.**
7
+
8
+ You do not have to use either. A game with no platform currency and no achievements is a complete
9
+ game, and the currencies your core loop runs on — gold, XP, energy, materials, cards — are yours to
10
+ design however you like ([inventory-and-catalogs.md](inventory-and-catalogs.md)). Read this page
11
+ before you design a reward a player carries out of your game, a price they pay with something
12
+ earned elsewhere, or an achievement. Most of the rules below are enforced at upload, so a design
13
+ that ignores them is refused rather than shipped.
14
+
15
+ ⚠️ **Gems are retiring.** Every player's Gems become Coins on the date the platform announces, and
16
+ nobody loses value. From then on a price in `GEMS` is refused (`422 platform_currency_unavailable`)
17
+ and a Gem grant pays nothing; uploads already warn on `GEMS`. Move now: reprice every Gem price in
18
+ Coins at **5 Coins per Gem**, move each Gem grant to a Coins grant at the rate in
19
+ [§4](#4-earning-needs-server-authority), and take "Gems" out of your copy. Add no new Gem content.
20
+
21
+ ## 1. The two, side by side
22
+
23
+ | | **Coins** (`ARCADE_TICKETS`) | **Achievement Points** |
24
+ | ------------------ | ------------------------------------------------------------------------------- | --------------------------------------------------------- |
25
+ | What it is | the arcade's one currency | permanent prestige: the **Achievement Score** |
26
+ | Which games pay it | any game with a server or a serverless function | an achievement, from any game with a server or a function |
27
+ | What it buys | anything a game prices in it, as its spend scope allows (§5) | nothing — it is never spent |
28
+ | Sold for money | **by the Arcade only**, in Coin packs; never by a game | never |
29
+ | Raised by VIP | yes — +50% on every grant, and the caps with it | never |
30
+ | Who may spend it | a player with a full account; a guest earns and keeps, and spends after sign-up | — |
31
+
32
+ A player earns Coins by playing almost anything, may buy more in the Arcade, and spends them in any
33
+ game that prices in them: upgrades, unlocks, extra attempts, characters, cards, skins, outfits,
34
+ effects, emotes. Achievement points are what a player has done, summed across every game into the
30
35
  Achievement Score; they are never bought, boosted or spent, and nothing takes them away except the
31
36
  platform, for fraud or moderation.
32
37
 
@@ -36,44 +41,38 @@ The arcade does not replace your economy. The currencies and resources your core
36
41
  gold, XP, wood, energy, lives, cards — stay your game's own: defined in your game config, minted and
37
42
  priced by you, invisible to every other game. Nothing on this page applies to them.
38
43
 
39
- What you map onto the three is the layer above that loop — the part that would otherwise become a
40
- platform-wide currency of your own:
44
+ What you map onto Coins and achievements is the layer above that loop — the part that would
45
+ otherwise become a platform-wide currency of your own:
41
46
 
42
47
  | If your design has… | Use |
43
48
  | ----------------------------------------------------------------------------------------- | --------------------------------------------------------- |
44
- | a premium or meta currency that speeds up or unlocks progress | **Gems**: price that progress in `GEMS` |
45
- | a cosmetics currency, or a shop of skins | **Coins**: price the cosmetics in `ARCADE_TICKETS` |
49
+ | a premium or meta currency that speeds up or unlocks progress, or buys cosmetics | **Coins**: price it in `ARCADE_TICKETS` |
46
50
  | a trophy count, a mastery rank or a completion percentage that says "this player is good" | **achievements**, whose points join the Achievement Score |
47
51
 
48
- ⚠️ **Do not build a second Gems or a second Coins.** A game-local premium or cosmetic
49
- meta-currency — "Crystals" that buy your upgrades, "Shards" that buy your skins, earned and hoarded
50
- the way a player would earn and hoard the arcade's — competes with the arcade's own, splits the
51
- player between two wallets that mean the same thing, and sidesteps every rule on this page: the Gem
52
- Source approval, the spend-scope line and the platform's caps. The release review checks for one.
53
- A currency that is part of play is fine: gold picked up in a level and spent in that run's shop,
54
- energy that paces a session, materials a crafting tree consumes.
52
+ ⚠️ **Do not build a second Coins.** A game-local premium or meta-currency — "Crystals" that buy your
53
+ upgrades, "Shards" that buy your skins, earned and hoarded the way a player would earn and hoard the
54
+ arcade's — competes with Coins, splits the player between two wallets that mean the same thing, and
55
+ sidesteps every rule on this page: the spend-scope line and the platform's caps. The release review
56
+ checks for one. A currency that is part of play is fine: gold picked up in a level and spent in that
57
+ run's shop, energy that paces a session, materials a crafting tree consumes.
55
58
 
56
- ## 3. Which kind of game is yours
59
+ Which platform currencies **your** game may grant or price is the platform's answer, and it can
60
+ change: `gemctl platform config --game <id>` lists them under `platform_grants` and
61
+ `platform_prices`. A mapping onto a currency the platform will not let your game pay is refused at
62
+ upload, and the refusal names the ones it will.
57
63
 
58
- There are four shapes:
64
+ ## 3. A game never sells currency for money
59
65
 
60
- | Shape | Grants Gems | Prices in Gems | For example |
61
- | -------------- | ----------- | -------------- | --------------------------------------------------------------------------------------- |
62
- | **Gem Source** | yes | no | a game with a controlled daily activity that feeds progress to the arcade |
63
- | **Balanced** | yes | yes | a Gem Source whose own progression also spends Gems |
64
- | **Gem Sink** | no | yes | a progression-heavy game: upgrades, characters, cards bought with Gems earned elsewhere |
65
- | **No-Gem** | no | no | a game that stays out of Gems entirely |
66
+ **Only the Arcade sells Coins.** A Coin pack is the Arcade Store's product, priced, sold and paid
67
+ for in the Arcade's own checkout. Your game may put the Arcade's offers in front of a player from a
68
+ button of its own, through `gem.store` ([arcade-and-store.md](arcade-and-store.md)), and that is
69
+ the whole of its part:
66
70
 
67
- The shapes describe what a game does, not what it may do: a Gem Source that starts pricing its own
68
- progress in Gems is Balanced. Every shape may pay Coins, price its cosmetics in Coins and carry
69
- achievements. **Gems can be your game's soft currency** — award them for play and price your
70
- progression in them.
71
-
72
- Which platform currencies **your** game may grant or price is the platform's answer, and it can
73
- change: `gemctl platform config --game <id>` lists them under `platform_grants` and
74
- `platform_prices`, each with a note on what the arcade wants it used for. A mapping onto a currency
75
- the platform will not let your game pay is refused at upload, and the refusal names the ones it
76
- will.
71
+ - **No currency for money.** Your game never sells Coins, a game currency or anything that grants
72
+ one for real money, alone or in a bundle.
73
+ - **No top-up screen of your own** ([§8](#8-the-arcade-owns-the-display-of-every-platform-currency)).
74
+ - **Bought and earned Coins spend the same.** Your game cannot tell them apart and prices nothing
75
+ differently for either.
77
76
 
78
77
  ## 4. Earning needs server authority
79
78
 
@@ -86,13 +85,13 @@ What may move it:
86
85
  - **Your game server** — a platform-allocated dedicated server, through the progression add and max
87
86
  routes.
88
87
  - **Your serverless function**, called by the player it pays, on a progression that sets
89
- `function_may_grant: true`. It may pay Gems and Coins and unlock achievements for **that player
90
- only**, and never for a guest. A function a player calls may also change only that player's own
91
- state — any write it aims at another player is refused. Opting in makes the function's own checks
92
- the only thing deciding a reward was earned, so read
88
+ `function_may_grant: true`. It may pay Coins and unlock achievements for **that player only**,
89
+ and never for a guest. A function a player calls may also change only that player's own state —
90
+ any write it aims at another player is refused. Opting in makes the function's own checks the
91
+ only thing deciding a reward was earned, so read
93
92
  [serverless-functions.md](serverless-functions.md) before you set it.
94
93
  - **Never the browser.** A mapped progression cannot be `client_writable`, and nothing else in your
95
- config — a recipe, a loot-table entry, another progression's reward — may write it.
94
+ config — a recipe, a loot-table entry, another progression's reward, a bonus — may write it.
96
95
 
97
96
  So a browser-only game with neither a server nor a function pays nothing platform-wide. That is the
98
97
  rule read strictly, not a gap: a browser credential is public, and anything it could pay, a modified
@@ -104,156 +103,154 @@ to-dos. A one-time prize, such as finishing the tutorial or a first-time milesto
104
103
  stays modest. Work the rate out from what each firing pays and how often a player fires it, and keep
105
104
  it between 250 and 1,000 an hour: the release review estimates it, and a rate outside that band
106
105
  blocks release unless the creator has approved it for that game. The platform's ceilings
107
- ([§6](#6-every-number-is-the-platforms-and-it-moves)) still trim each grant and each day, so at this
108
- rate a player reaches the daily maximum only after a long day of play; read the ceilings from
109
- `gemctl platform config`, never from here.
106
+ ([§6](#6-every-number-is-the-platforms-and-it-moves)) still trim each grant and each day; read them
107
+ from `gemctl platform config`, never from here.
108
+
109
+ **Configure the unboosted figure, and let the platform raise it.** A mapping's `amount` or
110
+ `per_unit` is what a player with no VIP and no Boost earns. The platform applies VIP's +50%, or a
111
+ Boost, itself when it pays ([§7](#7-vip-and-what-a-player-buys-with-money)).
112
+
113
+ **Show a payout with the platform's answer, never your own arithmetic.** Before a claim,
114
+ `gem.gameConfig.preview(target)` says what this player will get, what VIP would add, and which
115
+ bonuses apply; after it, `gem.earnings.fromReceipt(change)` says what they got, in the same shape
116
+ ([inventory-and-catalogs.md](inventory-and-catalogs.md#showing-a-payout-before-and-after-preview-and-fromreceipt)).
117
+ ⚠️ **Never multiply by `gem.player.boost`, and never copy a config amount into your game's code.**
118
+ Both go wrong the moment the platform retunes a number, and the release review flags hand-written
119
+ multiplier code.
110
120
 
111
121
  **Achievements hold the same line, harder.** An achievement that awards points may not sit on a
112
- progression anything in your config writes: a recipe priced in Gems that adds to it would sell the
122
+ progression anything in your config writes: a recipe priced in Coins that adds to it would sell the
113
123
  achievement. Points are never bought, boosted, spent, decayed or expired, and the platform removes
114
124
  them only for fraud or moderation ([achievements.md](achievements.md)).
115
125
 
116
- ## 5. Spending: price each item in the currency it deserves
126
+ ## 5. Spending: pricing in Coins
117
127
 
118
- Any game may sell an item or a recipe for a platform currency, with no server and no approval: the
119
- item or recipe carries `platform_prices`, and the player pays through the arcade's purchase sheet,
128
+ Any game may sell an item or a recipe for Coins, with no server and no approval: the item or recipe
129
+ carries `platform_prices`, and the player pays through the arcade's purchase sheet,
120
130
  `gem.purchases.request`
121
- ([inventory-and-catalogs.md](inventory-and-catalogs.md#16-selling-for-a-platform-currency)). What a
122
- currency may price is a platform setting on that currency, its **spend scope**:
123
-
124
- - **Gems — `any`.** Price progress in `GEMS`: an extra life, an upgrade, a character, a pack of
125
- cards, another attempt.
126
- - **Coins — `cosmetic_only` today.** Since 25 Oct 2026 Coins may price play items as well as
127
- cosmetics, and the platform moves Coins' scope to `any` to allow it; read the scope in
128
- `gemctl platform config`. While it is `cosmetic_only`, a Coins price is accepted only on an item your config declares
129
- `category: cosmetic`, or on a recipe that grants cosmetic items and nothing else. A Coins price
130
- on anything that grants a currency, moves a progression, grants a non-cosmetic item, opens into
131
- other items (a bundle or a loot box) or rolls a loot table is refused at upload — a loot table
132
- even when every entry in it is cosmetic.
133
-
134
- So while the scope is `cosmetic_only`, a Coins price on an item is always the pair
135
- `currency: ARCADE_TICKETS` and `category: cosmetic`; on a recipe, every item it grants declares
136
- `category: cosmetic`. Once it is `any`, a play item may carry a Coins price and keeps
137
- `category: progression`.
131
+ ([inventory-and-catalogs.md](inventory-and-catalogs.md#16-selling-for-a-platform-currency)).
132
+
133
+ - **What Coins may price is their spend scope**, a platform setting. Coins' is `any`, so they may
134
+ price play items and cosmetics alike. The platform owns the scope and can change it; read it in
135
+ `gemctl platform config`, and take it from a refusal rather than from this page.
136
+ - **Spending needs a full account.** The platform refuses a guest's spend with
137
+ `403 guest_upgrade_required`, and the arcade takes the player through sign-up. A guest keeps
138
+ earning, and their Coins carry over when they sign up.
139
+ - **A paid roll is gated.** A recipe priced in Coins that rolls a loot table, or carries a bonus
140
+ that rolls, is a paid random item: the arcade's purchase sheet shows the odds before the player
141
+ pays, and the platform refuses it for a known minor and in restricted regions
142
+ (`paid_chance_restricted`). Show the odds in your own shop too
143
+ ([inventory-and-catalogs.md](inventory-and-catalogs.md#12-loot-tables-and-odds)).
138
144
 
139
145
  **How much to charge: 500 Coins is worth about $1.** Price anything in Coins at what it would be
140
146
  worth in dollars, times 500: a $1 emote is 500 Coins, a $3 outfit 1,500, a $10 skin 5,000. A play
141
- item is worth what its counterpart costs in the hit games of its kind. At the
142
- earn rate in [§4](#4-earning-needs-server-authority), that is about an hour of play for each dollar.
143
- Coins are never sold for money, so the dollar is your yardstick only: never show a dollar figure
144
- beside a Coins price. The release review lists every Coins price with its dollar value and hours
145
- of play, and a price under 100 or over 10,000 Coins blocks release unless the creator has approved
146
- it for that game.
147
+ item is worth what its counterpart costs in the hit games of its kind. At the earn rate in
148
+ [§4](#4-earning-needs-server-authority), that is about an hour of play for each dollar. Never show a
149
+ dollar figure beside a Coins price. The release review lists every Coins price with its dollar value
150
+ and hours of play, and a price under 100 or over 10,000 Coins blocks release unless the creator has
151
+ approved it for that game.
147
152
 
148
- **`category` defaults to `progression`**, so an item is a cosmetic only because you said so. The
149
- platform takes that at your word and the release review checks it: an item that changes what a
150
- player can do, labelled `cosmetic` so it can sell for Coins, breaks platform policy.
153
+ **`category` still matters.** It defaults to `progression`; mark an item `category: cosmetic` only
154
+ when it changes nothing but how the player looks. The release review checks the label, and the
155
+ platform reads it whenever a currency's scope is `cosmetic_only`.
151
156
 
152
157
  **A cosmetic can still wait for progress.** "Reach level 10 to unlock the dragon skin" is a recipe
153
- with a prerequisite and a Coins price, and the platform refuses a locked player with
158
+ with a requirement and a Coins price, and the platform refuses a locked player with
154
159
  `recipe_locked` before it takes anything ([game-economy-tutorial.md](game-economy-tutorial.md)).
155
160
 
156
- **Take the scope from the refusal, not from this page.** The platform owns each currency's scope and
157
- can change it. A refusal names the scope that applied, and `gemctl platform config` lists which
158
- currencies your game may price in at all. A scope changed after you published stops the prices it
159
- no longer admits: the purchase is refused and the price leaves the catalog, with nothing charged.
160
-
161
161
  **The arcade sells cosmetics of its own for Coins, and never sells prestige.** Besides your game's
162
162
  shop, the arcade has a Coins shop of its own profile cosmetics. Every arcade item is either a
163
163
  **shop** item — a cosmetic priced in Coins — or **earned**: a default, a free claim, an achievement
164
- or Achievement Score reward. An earned item can never be given a price, in Coins, Gems or anything
165
- else, so what a player shows off for what they did cannot be bought.
164
+ or Achievement Score reward. An earned item can never be given a price, so what a player shows off
165
+ for what they did cannot be bought.
166
166
 
167
167
  ## 6. Every number is the platform's, and it moves
168
168
 
169
- Each platform currency carries settings the arcade holds per currency and can change at any time,
170
- without a release of this SDK or of your game:
169
+ Each platform currency carries settings the arcade holds and can change at any time, without a
170
+ release of this SDK or of your game:
171
171
 
172
172
  - which games may grant it;
173
173
  - how much one grant may pay, how much one game may pay one player per window, and how much all
174
174
  games together may pay one player per day — the **daily maximum**;
175
175
  - the most a player may hold — the **holdings cap**;
176
176
  - whether games may price in it, and what it may price;
177
- - whether a Boost raises it;
177
+ - whether VIP or a Boost raises it;
178
178
  - whether the arcade shows it in its header and wallet.
179
179
 
180
- **Author against `gemctl platform config`, never against a number you remember.** For your game it
181
- lists every currency a `platform_grants` mapping may target, with its current ceilings, every
182
- currency a price may name, and a note on when to reach for each. A currency it does not list for a
183
- site cannot be used there.
180
+ **Author against `gemctl platform config`, never against a number you remember.**
184
181
 
185
182
  Your game declares no cap of its own. You choose only what each firing pays; the ceilings bound it,
186
183
  and the order's `platform_grant` entry — `fired`, `clamped` or `suppressed` — is the receipt for what
187
184
  landed:
188
185
 
189
- - **A ceiling trims; it does not refuse.** For every platform currency, a firing that would cross
190
- the per-firing cap, the per-game cap or the daily maximum pays what is left of it and reads
191
- `clamped`; once the day is spent it pays 0. When the player reaches the daily maximum, the arcade
192
- tells them in its own chrome, and that notice is never relayed into your game
186
+ - **A ceiling trims; it does not refuse.** A firing that would cross the per-firing cap, the
187
+ per-game cap or the daily maximum pays what is left of it and reads `clamped`; once the day is
188
+ spent it pays 0. When the player reaches the daily maximum, the arcade tells them in its own
189
+ chrome, and that notice is never relayed into your game
193
190
  ([daily-challenges.md](daily-challenges.md)), though it can read how much more it may pay
194
191
  ([the earn allowance](inventory-and-catalogs.md#showing-how-much-more-a-player-can-earn)).
195
192
  - **The holdings cap is all or nothing.** A grant that would take the player's balance past it pays
196
193
  nothing and reads `clamped` with `clamped_by: holdings_cap`. Nothing a player already holds is
197
194
  ever taken away.
198
195
 
199
- Your reward screen may say what the mapping pays; the receipt, never your screen, says what the
200
- player got.
201
-
202
- ## 7. What a player buys with money: Gems and Boosts
203
-
204
- Nothing sells Coins or achievement points, alone or in a bundle. The arcade's own store sells
205
- Gems, and the Gems a player bought there spend in your game exactly as the ones they earned do: your
206
- game cannot tell the two apart and prices nothing differently for either. Your game never sells
207
- either itself. It may put the arcade store's own Gem packs, Boosts and passes in front of a player
208
- from a button of its own, through `gem.store`, and the arcade prices them, opens its own checkout
209
- and takes the money ([arcade-and-store.md](arcade-and-store.md)). The real-money product that changes
210
- what your game pays is a **Boost**, which a player always buys in the arcade's own checkout:
211
-
212
- - **It multiplies what Gems and Coins grants pay, and the ceilings with them.** A player on a 2×
213
- Boost earns twice as much per grant, may earn twice as much per window and per day, and may hold
214
- more. When it ends, what they earned stays theirs.
215
- - **It never touches achievement points.** An unlock credits the same points for every player.
216
- - **Your game does nothing to apply it.** The platform multiplies your mapping's payout itself.
217
- Keep `amount` at the unboosted figure; the receipt reports what landed.
218
- - **None is on sale yet.** The first Boost, `booster_x2_30d`, is inactive until the arcade launches
219
- it, so today every player earns at the unboosted rate. Design for both.
220
-
221
- ⚠️ **Show a Boost; never gate on one.** Your game may tell a player they are boosted — "2× Coins"
222
- beside a reward, from `gem.player.boost`
223
- ([what-a-game-must-do.md](what-a-game-must-do.md#the-signed-in-players-boost-show-it-never-gate-on-it))
224
- — and must never make content, difficulty, power, access, prices, matchmaking or odds depend on
225
- whether they are. A Boost buys a faster stream of the arcade's currencies and nothing else. A level,
226
- a character or an advantage behind it is paying to win, and the release review refuses it: the full
227
- rule is [platform-rules.md](platform-rules.md#8-a-players-boost-is-for-display-never-a-gate).
196
+ A preview shows the uncapped figure, because the limits are private; the receipt says when a limit
197
+ trimmed the payout, and `gem.earnings.fromReceipt` shows it.
198
+
199
+ ## 7. VIP, and what a player buys with money
200
+
201
+ The Arcade Store sells **Coin packs** and **VIP**, a pass. Your game never sells either itself; it
202
+ may open the Arcade's own sheet from a button of its own
203
+ ([arcade-and-store.md](arcade-and-store.md)).
204
+
205
+ **VIP is a pass with +50% Coins.** While a player holds it:
206
+
207
+ - **Every Coins grant pays +50%, and the ceilings scale with it**, so the extra stays real at the
208
+ cap. Your game does nothing to apply it: keep `amount` at the unboosted figure. The highest
209
+ multiplier in force wins — a player who also holds an older 2x Boost earns 2x, not 3x — and what
210
+ they earned stays theirs when VIP ends.
211
+ - **It never touches achievement points.**
212
+ - **It can unlock more in your game, through your config.** A recipe or loot table may carry
213
+ `bonuses`, each with a `requires`; `{ kind: 'holds_pass', pass: 'vip' }` is VIP. The platform
214
+ judges it when the recipe runs, and a bonus never fails the base grant
215
+ ([inventory-and-catalogs.md](inventory-and-catalogs.md#bonuses-more-for-a-player-who-meets-a-requirement)).
216
+ - **A game can offer it at the moment it matters** — "VIP: +1 roll, 45 Coins" before a chest opens
217
+ — from `gem.gameConfig.preview`'s `upsells`, opened with `gem.store.showPass`. Upsell **before**
218
+ you grant, never after: the platform holds nothing back for later
219
+ ([inventory-and-catalogs.md](inventory-and-catalogs.md#offering-vip-before-a-reward-the-upsell)).
220
+
221
+ **The Boost is retired.** No Boost is on sale. A player who holds the 2x Boost keeps it until it
222
+ ends, and `gem.player.boost` still reports it.
223
+
224
+ ⚠️ **Gate on the pass, never on the multiplier.** What VIP unlocks is decided by `holds_pass`,
225
+ checked by the platform, or by a pass read on your server or function. `gem.player.boost` shows
226
+ VIP's +50% too, and it is for display only: never make content, difficulty, power, access, prices,
227
+ matchmaking or odds depend on it. The full rule is
228
+ [platform-rules.md](platform-rules.md#8-a-players-boost-is-for-display-never-a-gate).
228
229
 
229
230
  ## 8. The arcade owns the display of every platform currency
230
231
 
231
- The arcade draws the player's platform currencies in its own chrome while your game runs, and
232
- animates every payout into the right total. Gems and Coins follow the same rules:
232
+ The arcade draws the player's Coins in its own chrome while your game runs, and animates every
233
+ payout into the right total:
233
234
 
234
235
  - **No counter.** Keep no balance, running total or wallet of a platform currency on any screen of
235
236
  yours. Read `gem.platform.currencies.get()` to decide whether a price is affordable, never to draw
236
237
  a number. A balance can change while your game is paused, so read it again on every
237
238
  `onPause(false)` if you keep anything worked out from it, such as a greyed-out Buy button.
238
- - **No celebration of your own.** At a payout, cue the arcade's wallet with `gem.cueReward`, naming
239
- the currency that paid — `currencySlug: 'GEMS'` from the gem on your reward screen,
240
- `'ARCADE_TICKETS'` from the coin. If it returns `false`, do nothing. When your serverless function
241
- pays it, pass one `cueId` (a `crypto.randomUUID()`) to both the cue and the `gem.functions.invoke`
242
- call, and the reward flies once
239
+ - **No celebration of your own.** At a payout, cue the arcade's wallet with `gem.cueReward`,
240
+ `currencySlug: 'ARCADE_TICKETS'`, from the coin on your reward screen. If it returns `false`, do
241
+ nothing. When your serverless function pays it, pass one `cueId` (a `crypto.randomUUID()`) to
242
+ both the cue and the `gem.functions.invoke` call, and the reward flies once
243
243
  ([inventory-and-catalogs.md](inventory-and-catalogs.md#showing-the-reward-land-in-the-arcades-wallet)).
244
244
  - **No "daily max" of your own making.** Your game cannot see what every other game paid the player,
245
245
  and must not compute a maximum. To show how much more your game can pay, read
246
- `gem.platform.earnAllowance.get()` (one consolidated figure, display only); the arcade still tells
247
- the player when they reach the maximum.
248
- - **No top-up or payment screen of your own.** Never draw a "buy Coins" offer, a checkout, a
249
- wallet top-up, or anything shaped like the arcade's purchase sheet: a game screen that looks like
250
- platform payment chrome is an imitation of it, and players cannot tell the two apart. Your shop's
251
- own Buy button is yours, and so is a "Get Gems" button that calls `gem.store.purchase`, or a "Get
252
- VIP" button that calls `gem.store.showOffer`, from the player's press: the checkout each one
253
- leads to is the arcade store's own
254
- ([arcade-and-store.md](arcade-and-store.md)). When a purchase is refused for
255
- `insufficient_funds`, say so in your shop's own words ("Not enough Coins") and stop there: a
256
- refusal never opens the store by itself.
246
+ `gem.platform.earnAllowance.get()` (display only); the arcade still tells the player when they
247
+ reach the maximum.
248
+ - **No top-up or payment screen of your own.** Never draw a "buy Coins" offer, a checkout, a wallet
249
+ top-up, or anything shaped like the arcade's purchase sheet. Your shop's own Buy button is yours,
250
+ and so is a "Get Coins" button that calls `gem.store.purchase` with a Coin pack, or a "Get VIP"
251
+ button that calls `gem.store.showPass('vip')`, from the player's press: the checkout each one
252
+ leads to is the Arcade's own. When a purchase is refused for `insufficient_funds`, say so in your
253
+ shop's own words ("Not enough Coins") and stop there: a refusal never opens the store by itself.
257
254
  - **No amount at all when you are not connected to the arcade.** Hide every platform price and
258
255
  reward unless `gem.awaitBridge()` settled `connected`.
259
256
  - **Draw a currency with the art the SDK ships**: the icon beside an amount, the hero only on a
@@ -264,37 +261,40 @@ never computes the score, restates it, or turns it into a currency of its own.
264
261
 
265
262
  ## 9. What the arcade does not have
266
263
 
267
- - **No Stars, Tokens or Supporter Seals, and not the old `COINS` currency** (retired; not today's
268
- Coins, `ARCADE_TICKETS`). All four are retired. None of them can be
269
- granted, priced in or bought: a config naming one is refused at upload, and a mapping onto one
270
- that is still live reads `suppressed`. The Achievement Score is the arcade's measure of prestige.
271
- - **No Gems or Coins for money from a game**, alone or in a bundle. Only the arcade's own store
272
- sells Gems, and nothing sells Coins.
264
+ - **No Gems, once they retire**, and no Stars, Tokens, Supporter Seals or the old `COINS` currency
265
+ (retired; not today's Coins, `ARCADE_TICKETS`). None of them can be granted, priced in or bought:
266
+ a config naming one is refused at upload, and a mapping onto one reads `suppressed`.
267
+ - **No currency for money from a game**, alone or in a bundle. Only the Arcade sells Coins.
273
268
  - **No achievement points or earned items for anything a player pays for.**
274
- - **No content behind a Boost.** A Boost is shown, never checked.
269
+ - **No content behind a multiplier.** VIP's +50% and a Boost are shown, never checked; content goes
270
+ behind `holds_pass`.
271
+ - **Nothing held for later.** The platform keeps no pending or missed bonus and no voucher: a bonus
272
+ is judged when the recipe runs.
275
273
 
276
274
  ## 10. When an upload or a grant is refused
277
275
 
278
- | What you get | What happened | What to do |
279
- | ---------------------------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
280
- | a mapping refused, naming the currencies your game may pay | the platform does not let your game grant that currency | target one it lists — `gemctl platform config` has the current set |
281
- | a receipt `suppressed`, reason `not_allowlisted…` | the platform stopped letting your game grant that currency | target a currency `gemctl platform config` lists; paid balances stay |
282
- | a price refused, naming the spend scope `cosmetic_only` | a Coins price on something that is not a cosmetic | price it in `GEMS` while Coins' scope is `cosmetic_only`, or mark the item `category: cosmetic` only if it changes nothing but looks |
283
- | a receipt `suppressed`, reason `functions_not_opted_in` | your function wrote a paying progression that has not opted in | read [serverless-functions.md](serverless-functions.md), then set `function_may_grant: true` |
284
- | a receipt `suppressed`, reason `function_gems_not_enabled` | an older receipt, from before functions could pay Gems | nothing — a function pays Gems now, so its next firing pays |
285
- | `403 function_foreign_entity_write` | a function a player called wrote another player | take the player from the call's context, never its input |
286
- | a receipt `clamped` | a ceiling trimmed the payout, or the day was already spent | nothing — show what landed; `gemctl platform config` has the ceilings |
287
- | a receipt `clamped`, `clamped_by: holdings_cap` | the player already holds as much as they may | nothing — the player spends before they earn more |
276
+ | What you get | What happened | What to do |
277
+ | ---------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
278
+ | a mapping refused, naming the currencies your game may pay | the platform does not let your game grant that currency | target one it lists — `gemctl platform config` has the current set |
279
+ | an upload warning, or after the cut `422 platform_currency_unavailable`, on `GEMS` | Gems are retiring | reprice in `ARCADE_TICKETS` at 5 Coins per Gem, and move Gem grants to Coins |
280
+ | a receipt `suppressed`, reason `not_allowlisted…` | the platform stopped letting your game grant that currency | target a currency `gemctl platform config` lists; paid balances stay |
281
+ | a price refused, naming a spend scope | the currency's scope does not admit that item | price it in a currency whose scope does, or mark the item `category: cosmetic` only if it changes nothing but looks |
282
+ | `403 guest_upgrade_required` | a guest tried to spend Coins | nothing — the arcade takes them through sign-up |
283
+ | `paid_chance_restricted` | a paid roll for a known minor, or in a restricted region | nothing for the base of a free recipe; a bonus roll is skipped, a paid roll refused |
284
+ | a receipt `suppressed`, reason `functions_not_opted_in` | your function wrote a paying progression that has not opted in | read [serverless-functions.md](serverless-functions.md), then set `function_may_grant: true` |
285
+ | `403 function_foreign_entity_write` | a function a player called wrote another player | take the player from the call's context, never its input |
286
+ | a receipt `clamped` | a ceiling trimmed the payout, or the day was already spent | nothing — show what landed with `gem.earnings.fromReceipt` |
287
+ | a receipt `clamped`, `clamped_by: holdings_cap` | the player already holds as much as they may | nothing — the player spends before they earn more |
288
288
 
289
289
  ## What to read next
290
290
 
291
- - [`inventory-and-catalogs.md`](inventory-and-catalogs.md) — platform grants (§13) and selling for a
292
- platform currency (§16), in full.
293
- - [`game-economy-tutorial.md`](game-economy-tutorial.md) — a shop, step by step, including a
294
- cosmetic sold for Coins.
291
+ - [`inventory-and-catalogs.md`](inventory-and-catalogs.md) — platform grants (§13), requirements and
292
+ bonuses (§9), the payout preview and selling for a platform currency (§16), in full.
293
+ - [`arcade-and-store.md`](arcade-and-store.md) — the Arcade Store, `showPass` and the VIP read.
294
+ - [`game-economy-tutorial.md`](game-economy-tutorial.md) — a shop, step by step.
295
295
  - [`daily-challenges.md`](daily-challenges.md) — the commonest thing that pays a platform currency.
296
296
  - [`achievements.md`](achievements.md) — achievements, their points and the Achievement Score.
297
297
  - [`serverless-functions.md`](serverless-functions.md) — paying a player from a game with no
298
298
  dedicated server, and what a function a player calls may change.
299
- - [`platform-rules.md`](platform-rules.md) — the Boost rule, among the rest.
299
+ - [`platform-rules.md`](platform-rules.md) — gate on the pass, never on the multiplier.
300
300
  - [`brand-art.md`](brand-art.md) — the art for every platform currency, and the rules for drawing it.
package/docs/changelog.md CHANGED
@@ -16,6 +16,54 @@ Its entries are grouped under up to five headings:
16
16
  - **Rebuild**: a type changed. Recompile against the new version; code that compiles today keeps compiling unless the entry says otherwise.
17
17
  - **New capability**: something a game can now do. Nothing changes unless you use it.
18
18
 
19
+ ## 24.0.0
20
+
21
+ Upgrading from 23.3.0. Impact: required.
22
+
23
+ ### Must change
24
+
25
+ - Stop using the store offers call answering sign_in_required as a test for a guest. A current Arcade now answers a guest with ok, because a guest can buy. Use GemPlayer.isGuest instead. It is true only when the Arcade says the player is a guest who can sign up here, and false for a full account, for a surface with no sign-up, and for an older Arcade that does not say. It follows a guest who signs up in place.
26
+ - Check any code that branches on the offer kind. ArcadeOffer.kind is now typed as ArcadeOfferKind, and a pack of a platform currency other than GEMS is reported as coin_pack rather than gem_pack. The gem_pack value is deprecated. Handle coin_pack wherever you handle gem_pack, or such packs will fall through your branches.
27
+ - If your tests run the parent conformance kit against an Arcade host or a stand-in for one, update them for kit version 9. The version constant moved from 8 to 9, there is a new storeScenario member on the parent under test, and there are six new pass cases. Supply a store scenario so those cases can run.
28
+
29
+ ### Should change
30
+
31
+ - GamePass.endsAt is deprecated on the browser, function and server halves. An exact end instant is a value two games could match to link one player's identities, so the platform will stop sending it and it will read null. Move to GamePass.daysLeft, which gives whole days remaining, capped at 7. A known guest is answered as not holding the pass. See [the deprecation policy](deprecation-policy.md#4-what-is-deprecated-today).
32
+
33
+ ### May stop
34
+
35
+ - If your game works out expected rewards or the effect of a player's boost by hand, you can drop that arithmetic. The config preview now computes the multiplier, platform grants with their rounding, and bonus eligibility for you.
36
+
37
+ ### Rebuild
38
+
39
+ - Types changed in two places. The store offers result now allows an optional pass and optional earn multipliers on each offer. The server match object gains playerPasses and playerBoost. Recompiling is enough, unless your code supplies its own stand-in for the match object, which then needs the two new members.
40
+
41
+ ### New capability
42
+
43
+ - Offers now say which pass they sell and which earn multipliers they carry. Each ArcadeOffer has an optional pass and optional earnMultipliers, so a game can show what an offer raises before the player buys it.
44
+ - A game can sell a pass by its slug. StoreApi.passOnSale tells you whether the pass is on sale here, and StoreApi.showPass opens its sheet. The ShowPassOutcome it returns is ok only once a fresh read of the player's passes shows the pass held, and otherwise ok or pending as that type describes.
45
+ - A game can preview what a config change would grant before applying it. You pass a config id, never an amount or a pass. The preview works out the player's multiplier, holdings and local offers, and reports grants, platform grants with a breakdown crediting a held pass or Boost, bonuses with eligibility and unmet requirements, and upsells for passes the player could buy. Rolls are reported as a count from a table, never an outcome. Errors and targets have their own types.
46
+ - Earnings can be read from a receipt. The new earnings API turns a receipt's platform grant change into the same platform grant shape the preview uses, from the base amount granted and the boost source. It is available on the browser, on the function half, and on the server and match server. The function half resolves the currency from the invoking player's currencies, while the server half takes a currency id only.
47
+ - A dedicated game server can now read a player's passes and Boost in a match. Use the match player passes read to gate on a pass, using the same GamePass shape as the browser. The match player Boost read returns a MatchPlayerBoost with its multipliers, for display only. Both reads go through optional runtime hooks, host.passes and host.boost, and an image without a hook, or a listen host, is refused by name. A refusal surfaces as an API error that keeps its original code.
48
+
49
+ ## 23.3.0
50
+
51
+ *Written from the API facts alone; no summary was generated for this release.*
52
+
53
+ Upgrading from 23.2.0. Impact: required.
54
+
55
+ ### Must change
56
+
57
+ - FrameCallbacks.onPlayer changed its signature.
58
+ - FrameOptions.onPlayer changed its signature.
59
+
60
+ ### New capability
61
+
62
+ - AccountApi.onSignInAvailableChange was added.
63
+ - AccountApi.signInAvailable was added.
64
+ - GemPlayer.signInAvailable was added.
65
+ - SendPlayerInput.signInAvailable was added.
66
+
19
67
  ## 23.2.0
20
68
 
21
69
  *Written from the API facts alone; no summary was generated for this release.*
@@ -100,6 +100,8 @@ deprecated.
100
100
  | `GemPlayer.displayHandle` | `PlayerIdentity.displayTag` — the same value | `7.0.0` | Package API surface |
101
101
  | `GemPlayer.avatarUrl` | `PlayerIdentity.avatarIconUrl` — the same value | `7.0.0` | Package API surface |
102
102
  | `AdRequest.format: 'interstitial'` | `gem.moments.signal` — a platform moment at the same break, from the press that leaves it | `14.0.0` | Package API surface |
103
+ | `GamePass.endsAt` | `GamePass.daysLeft` — whole days, capped at 7 | `24.0.0`, for the member | Package API surface, plus runtime delivery |
104
+ | `ArcadeOffer.kind: 'gem_pack'` | `'coin_pack'` — Gems are retired, and Coins are the one platform currency | `24.0.0` | Package API surface |
103
105
  | `profile(playerId)` | The room read's per-member display identity | Not a package release | Runtime delivery only |
104
106
 
105
107
  **"For the member" is the split from section 3**, and it is the row's real answer: `4.0.0` is
@@ -50,7 +50,7 @@ either way.
50
50
  ```jsonc
51
51
  // package.json
52
52
  "dependencies": {
53
- "@indiegems/gem-web-sdk": "23.2.0"
53
+ "@indiegems/gem-web-sdk": "24.0.0"
54
54
  }
55
55
  ```
56
56