@indiegems/gem-web-sdk 16.0.1 → 16.2.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.
- package/CHANGELOG.md +30 -0
- package/dist/function.d.ts +27 -3
- package/dist/game.d.ts +29 -4
- package/dist/gem-web-sdk.bridge.js +2 -2
- package/dist/gem-web-sdk.conformance.js +3 -3
- package/dist/gem-web-sdk.esm.js +3 -3
- package/dist/gem-web-sdk.function.js +3 -3
- package/dist/gem-web-sdk.game.js +3 -3
- package/dist/gem-web-sdk.min.js +3 -3
- package/dist/gem-web-sdk.sdk.js +3 -3
- package/dist/gem-web-sdk.server.js +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/sdk.d.ts +31 -4
- package/dist/server-api.d.ts +3 -0
- package/dist/server.d.ts +3 -0
- package/docs/api/api-reference-function.md +169 -5
- package/docs/api/api-reference-game.md +192 -8
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +163 -19
- package/docs/changelog.md +30 -0
- package/docs/getting-started.md +1 -1
- package/docs/manifest.json +10 -10
- package/docs/platform-moments.md +32 -1
- package/docs/saving-player-progress.md +171 -5
- package/docs/server-bundles.md +2 -2
- package/package.json +1 -1
- package/schema/bridge.v1.json +5 -0
package/docs/api/reference.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"source": {
|
|
4
4
|
"repo": "IndieGems/gem-web-sdk",
|
|
5
|
-
"version": "16.0
|
|
5
|
+
"version": "16.2.0"
|
|
6
6
|
},
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -2805,7 +2805,7 @@
|
|
|
2805
2805
|
"group": "saves",
|
|
2806
2806
|
"signature": "readonly etag: string",
|
|
2807
2807
|
"documented": true,
|
|
2808
|
-
"summary": "This save's version, which changes whenever the save does.\n\nNOT something to pass back on a write —
|
|
2808
|
+
"summary": "This save's version, which changes whenever the save does.\n\nNOT something to pass back on a write — the SDK remembers the version of\neach slot you last loaded or wrote and sends it for you, so a write that\nwould replace a save you never saw is refused as `save_conflict`. This is\nhow a game holding a copy tells whether the stored save is still the one\nit has.",
|
|
2809
2809
|
"deprecated": null,
|
|
2810
2810
|
"params": [],
|
|
2811
2811
|
"returns": null,
|
|
@@ -2936,9 +2936,9 @@
|
|
|
2936
2936
|
"name": "reason",
|
|
2937
2937
|
"anchor": "gemsaveerror-reason",
|
|
2938
2938
|
"group": "failures",
|
|
2939
|
-
"signature": "readonly reason: \"upload_failed\" | \"download_failed\" | \"save_timeout\" | \"save_malformed\"",
|
|
2939
|
+
"signature": "readonly reason: \"upload_failed\" | \"download_failed\" | \"save_timeout\" | \"save_malformed\" | \"save_conflict\" | \"load_failed\"",
|
|
2940
2940
|
"documented": true,
|
|
2941
|
-
"summary": "Which part failed.\n\n- `upload_failed` — the bytes could not be written to storage.\n- `download_failed` — the stored bytes could not be fetched.\n- `save_timeout` — a hop did not finish in the time the call allows.\n- `save_malformed` — the answer, or the stored payload, was not what it\n claims to be.\n\n`save_timeout`, and a one-off `upload_failed` or `download_failed`, are\nworth one retry. `save_malformed` is not: the same bytes will fail the\nsame way.\n\nAn `upload_failed` that repeats on EVERY save, for every player, is not\ntransient either — it is a platform-side misconfiguration, and retrying\nis a loop rather than a recovery. See the saves guide.",
|
|
2941
|
+
"summary": "Which part failed.\n\n- `upload_failed` — the bytes could not be written to storage.\n- `download_failed` — the stored bytes could not be fetched.\n- `save_timeout` — a hop did not finish in the time the call allows.\n- `save_malformed` — the answer, or the stored payload, was not what it\n claims to be.\n- `save_conflict` — a write was refused because the stored save changed\n since this game last loaded or wrote it: another device or tab saved\n in between. Nothing was written.\n- `load_failed` — a write was refused before anything was sent, because\n the last load of this slot failed, so what the write would replace is\n unknown — it could be the player's real progress.\n\n`save_timeout`, and a one-off `upload_failed` or `download_failed`, are\nworth one retry. `save_malformed` is not: the same bytes will fail the\nsame way.\n\nNeither is `save_conflict` or `load_failed`: resending the same write\nfails the same way. Load the slot again and decide what to write from\nwhat is stored — merge, or ask the player. Pass `overwrite: true` only to\nreplace whatever is stored on purpose.\n\nAn `upload_failed` that repeats on EVERY save, for every player, is not\ntransient either — it is a platform-side misconfiguration, and retrying\nis a loop rather than a recovery. See the saves guide.\n\nA RATE LIMIT IS NOT ONE OF THESE. Refused for the rate on any leg — the\nplatform, the upload, the download — a save or load rejects as\n`GemApiError` reason `rate_limited`, with `retryAfterSeconds` when a wait\nwas named.",
|
|
2942
2942
|
"deprecated": null,
|
|
2943
2943
|
"params": [],
|
|
2944
2944
|
"returns": null,
|
|
@@ -2991,7 +2991,7 @@
|
|
|
2991
2991
|
"group": "saves",
|
|
2992
2992
|
"signature": "readonly signal?: AbortSignal",
|
|
2993
2993
|
"documented": true,
|
|
2994
|
-
"summary": "Cancel the write.\n\nA save is several hops, so a cancelled write can land after the platform\nhas already agreed to it. Re-read rather than assuming nothing was stored.",
|
|
2994
|
+
"summary": "Cancel the write.\n\nA save is several hops, so a cancelled write can land after the platform\nhas already agreed to it. Re-read rather than assuming nothing was stored.\n\nA write that replaced earlier ones still waiting on its slot carries them,\nso cancelling it before it goes out cancels them too. Cancelling a write\nthat a later one replaced rejects only this call; the later write goes on.",
|
|
2995
2995
|
"deprecated": null,
|
|
2996
2996
|
"params": [],
|
|
2997
2997
|
"returns": null,
|
|
@@ -3007,7 +3007,39 @@
|
|
|
3007
3007
|
"group": "saves",
|
|
3008
3008
|
"signature": "readonly timeoutMs?: number",
|
|
3009
3009
|
"documented": true,
|
|
3010
|
-
"summary": "How long this whole save may take, every hop together. Overrides the 30\nsecond default.\n\nSET THIS WHEN YOU ARE RACING A CLOCK YOU DO NOT CONTROL — above all when\nsaving from [GemTerminationNotice](api-reference-game.md#gemterminationnotice), whose `graceMs` can be shorter\nthan the default. Left at the default in that case a save is killed\nmid-flight: the promise never settles, nothing is logged, and the player\nloses progress silently. Set it to the budget you actually have and a save\nthat will not make it rejects as `save_timeout` while there is still time\nto do something about it.\n\nIt does not make a save faster. Three round trips plus the payload is what\na save costs; this only decides when to stop waiting.",
|
|
3010
|
+
"summary": "How long this whole save may take, every hop together. Overrides the 30\nsecond default.\n\nSET THIS WHEN YOU ARE RACING A CLOCK YOU DO NOT CONTROL — above all when\nsaving from [GemTerminationNotice](api-reference-game.md#gemterminationnotice), whose `graceMs` can be shorter\nthan the default. Left at the default in that case a save is killed\nmid-flight: the promise never settles, nothing is logged, and the player\nloses progress silently. Set it to the budget you actually have and a save\nthat will not make it rejects as `save_timeout` while there is still time\nto do something about it.\n\nIt does not make a save faster. Three round trips plus the payload is what\na save costs; this only decides when to stop waiting.\n\nIt counts from the call, so a write waiting for the one before it on the\nsame slot spends it waiting. A rate limit whose wait it will not cover\nrejects at once as `GemApiError` reason `rate_limited`, not as\n`save_timeout`.\n\nIt stays this call's when a later write replaces this one: running out\nrejects only this call, and the later write goes on. The write that is\nsent runs on the newest call's `timeoutMs`, and running out of that rejects\nevery call it carries.",
|
|
3011
|
+
"deprecated": null,
|
|
3012
|
+
"params": [],
|
|
3013
|
+
"returns": null,
|
|
3014
|
+
"throws": null,
|
|
3015
|
+
"example": null,
|
|
3016
|
+
"defaultValue": null,
|
|
3017
|
+
"guides": []
|
|
3018
|
+
},
|
|
3019
|
+
{
|
|
3020
|
+
"id": "PutSaveOptions.teardown",
|
|
3021
|
+
"name": "teardown",
|
|
3022
|
+
"anchor": "putsaveoptions-teardown",
|
|
3023
|
+
"group": "saves",
|
|
3024
|
+
"signature": "readonly teardown?: boolean",
|
|
3025
|
+
"documented": true,
|
|
3026
|
+
"summary": "This is the last write before the page goes away: send it without waiting\nfor the spacing between writes to one slot.\n\nWrites to one slot go out a few seconds apart, and a write made sooner\nwaits its turn. Set this on the write you make from\n[GemTerminationNotice](api-reference-game.md#gemterminationnotice) or `pagehide`, with a `timeoutMs` inside the\ntime you have.\n\nIT STILL WAITS FOR A WRITE ALREADY IN FLIGHT to the same slot, and it\nstill honours a rate-limit hold: when the hold outlasts `timeoutMs` the\nwrite rejects at once as `rate_limited`, and nothing is sent. Not for\nordinary saves — the spacing is what keeps a save loop from spending the\nrate limit.\n\nA later write that replaces this one while it waits keeps it: the write\nthat goes out skips the spacing when any call it carries set `teardown`.\nNot across a player change: a call replaced after the signed-in player\nchanged is refused as `save_conflict`, and its `teardown` goes with it.",
|
|
3027
|
+
"deprecated": null,
|
|
3028
|
+
"params": [],
|
|
3029
|
+
"returns": null,
|
|
3030
|
+
"throws": null,
|
|
3031
|
+
"example": null,
|
|
3032
|
+
"defaultValue": null,
|
|
3033
|
+
"guides": []
|
|
3034
|
+
},
|
|
3035
|
+
{
|
|
3036
|
+
"id": "PutSaveOptions.overwrite",
|
|
3037
|
+
"name": "overwrite",
|
|
3038
|
+
"anchor": "putsaveoptions-overwrite",
|
|
3039
|
+
"group": "saves",
|
|
3040
|
+
"signature": "readonly overwrite?: boolean",
|
|
3041
|
+
"documented": true,
|
|
3042
|
+
"summary": "Write even if it replaces a save this game has not loaded.\n\nWithout it, a write to a slot whose last load FAILED is refused as\n`load_failed`, and a write is refused as `save_conflict` when the stored\nsave changed since you last loaded or wrote it. Both refusals protect the\nplayer's real progress from a game that started from defaults.\n\nSet it only when replacing whatever is stored is what you mean — a \"new\ngame\" the player confirmed, or the player choosing this device's progress\nafter a conflict. Never set it to get past a failed load.\n\nA later write that replaces this one while it waits keeps it: the write\nthat goes out overwrites when any call it carries set `overwrite`. A call\nthat gives up first (its `signal` or `timeoutMs`) takes it back, and so\ndoes one replaced after the signed-in player changed: it is refused as\n`save_conflict`, so one player's \"new game\" never overwrites the next's.",
|
|
3011
3043
|
"deprecated": null,
|
|
3012
3044
|
"params": [],
|
|
3013
3045
|
"returns": null,
|
|
@@ -3060,7 +3092,7 @@
|
|
|
3060
3092
|
"group": "saves",
|
|
3061
3093
|
"signature": "readonly signal?: AbortSignal",
|
|
3062
3094
|
"documented": true,
|
|
3063
|
-
"summary": "Cancel the write.\n\nA save is several hops, so a cancelled write can land after the platform\nhas already agreed to it. Re-read rather than assuming nothing was stored.",
|
|
3095
|
+
"summary": "Cancel the write.\n\nA save is several hops, so a cancelled write can land after the platform\nhas already agreed to it. Re-read rather than assuming nothing was stored.\n\nA write that replaced earlier ones still waiting on its slot carries them,\nso cancelling it before it goes out cancels them too. Cancelling a write\nthat a later one replaced rejects only this call; the later write goes on.",
|
|
3064
3096
|
"deprecated": null,
|
|
3065
3097
|
"params": [],
|
|
3066
3098
|
"returns": null,
|
|
@@ -3076,7 +3108,39 @@
|
|
|
3076
3108
|
"group": "saves",
|
|
3077
3109
|
"signature": "readonly timeoutMs?: number",
|
|
3078
3110
|
"documented": true,
|
|
3079
|
-
"summary": "How long this whole save may take, every hop together. Overrides the 30\nsecond default.\n\nSET THIS WHEN YOU ARE RACING A CLOCK YOU DO NOT CONTROL — above all when\nsaving from [GemTerminationNotice](api-reference-game.md#gemterminationnotice), whose `graceMs` can be shorter\nthan the default. Left at the default in that case a save is killed\nmid-flight: the promise never settles, nothing is logged, and the player\nloses progress silently. Set it to the budget you actually have and a save\nthat will not make it rejects as `save_timeout` while there is still time\nto do something about it.\n\nIt does not make a save faster. Three round trips plus the payload is what\na save costs; this only decides when to stop waiting.",
|
|
3111
|
+
"summary": "How long this whole save may take, every hop together. Overrides the 30\nsecond default.\n\nSET THIS WHEN YOU ARE RACING A CLOCK YOU DO NOT CONTROL — above all when\nsaving from [GemTerminationNotice](api-reference-game.md#gemterminationnotice), whose `graceMs` can be shorter\nthan the default. Left at the default in that case a save is killed\nmid-flight: the promise never settles, nothing is logged, and the player\nloses progress silently. Set it to the budget you actually have and a save\nthat will not make it rejects as `save_timeout` while there is still time\nto do something about it.\n\nIt does not make a save faster. Three round trips plus the payload is what\na save costs; this only decides when to stop waiting.\n\nIt counts from the call, so a write waiting for the one before it on the\nsame slot spends it waiting. A rate limit whose wait it will not cover\nrejects at once as `GemApiError` reason `rate_limited`, not as\n`save_timeout`.\n\nIt stays this call's when a later write replaces this one: running out\nrejects only this call, and the later write goes on. The write that is\nsent runs on the newest call's `timeoutMs`, and running out of that rejects\nevery call it carries.",
|
|
3112
|
+
"deprecated": null,
|
|
3113
|
+
"params": [],
|
|
3114
|
+
"returns": null,
|
|
3115
|
+
"throws": null,
|
|
3116
|
+
"example": null,
|
|
3117
|
+
"defaultValue": null,
|
|
3118
|
+
"guides": []
|
|
3119
|
+
},
|
|
3120
|
+
{
|
|
3121
|
+
"id": "PutBytesOptions.teardown",
|
|
3122
|
+
"name": "teardown",
|
|
3123
|
+
"anchor": "putbytesoptions-teardown",
|
|
3124
|
+
"group": "saves",
|
|
3125
|
+
"signature": "readonly teardown?: boolean",
|
|
3126
|
+
"documented": true,
|
|
3127
|
+
"summary": "This is the last write before the page goes away: send it without waiting\nfor the spacing between writes to one slot.\n\nWrites to one slot go out a few seconds apart, and a write made sooner\nwaits its turn. Set this on the write you make from\n[GemTerminationNotice](api-reference-game.md#gemterminationnotice) or `pagehide`, with a `timeoutMs` inside the\ntime you have.\n\nIT STILL WAITS FOR A WRITE ALREADY IN FLIGHT to the same slot, and it\nstill honours a rate-limit hold: when the hold outlasts `timeoutMs` the\nwrite rejects at once as `rate_limited`, and nothing is sent. Not for\nordinary saves — the spacing is what keeps a save loop from spending the\nrate limit.\n\nA later write that replaces this one while it waits keeps it: the write\nthat goes out skips the spacing when any call it carries set `teardown`.\nNot across a player change: a call replaced after the signed-in player\nchanged is refused as `save_conflict`, and its `teardown` goes with it.",
|
|
3128
|
+
"deprecated": null,
|
|
3129
|
+
"params": [],
|
|
3130
|
+
"returns": null,
|
|
3131
|
+
"throws": null,
|
|
3132
|
+
"example": null,
|
|
3133
|
+
"defaultValue": null,
|
|
3134
|
+
"guides": []
|
|
3135
|
+
},
|
|
3136
|
+
{
|
|
3137
|
+
"id": "PutBytesOptions.overwrite",
|
|
3138
|
+
"name": "overwrite",
|
|
3139
|
+
"anchor": "putbytesoptions-overwrite",
|
|
3140
|
+
"group": "saves",
|
|
3141
|
+
"signature": "readonly overwrite?: boolean",
|
|
3142
|
+
"documented": true,
|
|
3143
|
+
"summary": "Write even if it replaces a save this game has not loaded.\n\nWithout it, a write to a slot whose last load FAILED is refused as\n`load_failed`, and a write is refused as `save_conflict` when the stored\nsave changed since you last loaded or wrote it. Both refusals protect the\nplayer's real progress from a game that started from defaults.\n\nSet it only when replacing whatever is stored is what you mean — a \"new\ngame\" the player confirmed, or the player choosing this device's progress\nafter a conflict. Never set it to get past a failed load.\n\nA later write that replaces this one while it waits keeps it: the write\nthat goes out overwrites when any call it carries set `overwrite`. A call\nthat gives up first (its `signal` or `timeoutMs`) takes it back, and so\ndoes one replaced after the signed-in player changed: it is refused as\n`save_conflict`, so one player's \"new game\" never overwrites the next's.",
|
|
3080
3144
|
"deprecated": null,
|
|
3081
3145
|
"params": [],
|
|
3082
3146
|
"returns": null,
|
|
@@ -3108,7 +3172,7 @@
|
|
|
3108
3172
|
"group": "saves",
|
|
3109
3173
|
"signature": "class SavesApi",
|
|
3110
3174
|
"documented": true,
|
|
3111
|
-
"summary": "A player's saved progress — write it, read it, list it, delete it.\n\nReached as `gem.me.saves`, and on a character as\n`gem.characters.get(id).saves`.\n\nA SAVE GOES IN A SLOT YOU DECLARED. Slots live in your game config, published\nwith `gemctl`, next to your room templates. Writing to a slot that was never\ndeclared fails as `not_found` naming the key, on the first call and every\nretry — that is the wall almost everyone hits first, and it is a config\nproblem rather than a code one.\n\n```js\nawait gem.me.saves.put('slot1', { level: 7, checkpoint: 'bridge' });\nconst state = await gem.me.saves.get('slot1');\n```\n\nWHAT IS DELIBERATELY NOT HERE: no autosave timer, no save format, no\nmigration. Those are your game's, and a platform that guessed at them would\nguess wrong.",
|
|
3175
|
+
"summary": "A player's saved progress — write it, read it, list it, delete it.\n\nReached as `gem.me.saves`, and on a character as\n`gem.characters.get(id).saves`.\n\nA SAVE GOES IN A SLOT YOU DECLARED. Slots live in your game config, published\nwith `gemctl`, next to your room templates. Writing to a slot that was never\ndeclared fails as `not_found` naming the key, on the first call and every\nretry — that is the wall almost everyone hits first, and it is a config\nproblem rather than a code one.\n\n```js\nawait gem.me.saves.put('slot1', { level: 7, checkpoint: 'bridge' });\nconst state = await gem.me.saves.get('slot1');\n```\n\nLOAD BEFORE YOU SAVE. Each write carries the version of the slot your last\nload or write found, so a write that would replace a save this game has not\nseen — another device's, or the player's real progress after a load that\nfailed — is refused as `save_conflict` or `load_failed` rather than stored.\nAfter a failed load, play on without saving and load again later; pass\n`overwrite: true` only when the player chose to start over.\n\nONE WRITE PER SLOT AT A TIME. A write made while another to the same slot is\nin flight waits for it, and a later one replaces it: the newest value is the\none sent, and every caller it replaced resolves with it, keeping its own\n`signal`, `timeoutMs`, `teardown` and `overwrite`. Writes to one slot\ngo out a few seconds apart (`teardown: true` skips that), and after a\n`rate_limited` answer none goes out until the wait it named is over.\n\nWHAT IS DELIBERATELY NOT HERE: no autosave timer, no save format, no\nmigration. Those are your game's, and a platform that guessed at them would\nguess wrong.",
|
|
3112
3176
|
"deprecated": null,
|
|
3113
3177
|
"params": [],
|
|
3114
3178
|
"returns": null,
|
|
@@ -14198,6 +14262,22 @@
|
|
|
14198
14262
|
"defaultValue": null,
|
|
14199
14263
|
"guides": []
|
|
14200
14264
|
},
|
|
14265
|
+
{
|
|
14266
|
+
"id": "MomentSignal.wall",
|
|
14267
|
+
"name": "wall",
|
|
14268
|
+
"anchor": "momentsignal-wall",
|
|
14269
|
+
"group": "moments",
|
|
14270
|
+
"signature": "wall?: boolean",
|
|
14271
|
+
"documented": true,
|
|
14272
|
+
"summary": "Optional: `true` says your game accepts that the arcade may show its **app\nwall** at this break — a panel that may leave the player no way on in this\nbrowser. Set it only at a break where the player's progress is already\nsaved and stopping there is acceptable. Absent or `false` is an ordinary\nbreak. It is an acceptance, not an ask: the arcade still decides, and\nusually shows nothing. An older arcade ignores it and treats the break as\nordinary.",
|
|
14273
|
+
"deprecated": null,
|
|
14274
|
+
"params": [],
|
|
14275
|
+
"returns": null,
|
|
14276
|
+
"throws": null,
|
|
14277
|
+
"example": null,
|
|
14278
|
+
"defaultValue": null,
|
|
14279
|
+
"guides": []
|
|
14280
|
+
},
|
|
14201
14281
|
{
|
|
14202
14282
|
"id": "MomentResult",
|
|
14203
14283
|
"name": "MomentResult",
|
|
@@ -14253,14 +14333,14 @@
|
|
|
14253
14333
|
"group": "moments",
|
|
14254
14334
|
"signature": "signal(moment: MomentSignal): Promise<MomentResult>",
|
|
14255
14335
|
"documented": true,
|
|
14256
|
-
"summary": "Signal a platform moment: the player is leaving a natural break in play.\nThe arcade may show one of its own offers over your paused game, and\nusually shows nothing.\n\n**Call it from the player's press that LEAVES the break** — Next, Retry,\nBack to menu — once per break. Start your own transition in parallel, and\nstart play when this settles. **Never** mid-input, during a timed\nsequence, on a timer, on boot or the first arrival at the title, or from a\nplayer-opened pause menu (choosing Quit to menu from it is a\n`menu_return`). No press is required by the SDK; the rule is about when a\nbreak really is one.\n\nNOTHING REJECTS. It resolves `{ shown: false }` without asking for an\narcade that does not do platform moments, a `kind` or `placement` outside\nthe grammar, a call before
|
|
14336
|
+
"summary": "Signal a platform moment: the player is leaving a natural break in play.\nThe arcade may show one of its own offers over your paused game, and\nusually shows nothing.\n\n**Call it from the player's press that LEAVES the break** — Next, Retry,\nBack to menu — once per break. Start your own transition in parallel, and\nstart play when this settles. **Never** mid-input, during a timed\nsequence, on a timer, on boot or the first arrival at the title, or from a\nplayer-opened pause menu (choosing Quit to menu from it is a\n`menu_return`). No press is required by the SDK; the rule is about when a\nbreak really is one.\n\nAdd `wall: true` only at a break where the player's progress is saved and\nyour game accepts that the arcade may show its app wall there.\n\nNOTHING REJECTS. It resolves `{ shown: false }` without asking for an\narcade that does not do platform moments, a `kind` or `placement` outside\nthe grammar, a `wall` that is not a boolean, a call before\n`gem.frame.ready()`, a call while another is in flight, a call within 10\nseconds of the last answer, or no answer within\nthree minutes — and each of those, except an arcade that does not do it\n(said once a session), is reported to your `onError` handler with code\n`'internal'` and a message beginning with a stable token:\n`moments_unsupported`, `moments_invalid_argument`, `moments_before_ready`,\n`moments_in_flight`, `moments_called_repeatedly` or `moments_timeout`.\n\nThe arcade pauses and resumes your game through the ordinary\n`gem.pause`, so you do not pause anything yourself.\n\n⚠️ `shown` grants nothing. Never hand out a reward, an unlock or a currency\non it, and never pop your own offer at a break that settled `shown: true`.",
|
|
14257
14337
|
"deprecated": null,
|
|
14258
14338
|
"params": [
|
|
14259
14339
|
{
|
|
14260
14340
|
"name": "moment",
|
|
14261
14341
|
"type": "MomentSignal",
|
|
14262
14342
|
"optional": false,
|
|
14263
|
-
"summary": "Which kind of break, and
|
|
14343
|
+
"summary": "Which kind of break, your screen's name, and whether a wall is acceptable."
|
|
14264
14344
|
}
|
|
14265
14345
|
],
|
|
14266
14346
|
"returns": "Whether the arcade showed anything. Always resolves; never rejects.",
|
|
@@ -32505,7 +32585,7 @@
|
|
|
32505
32585
|
"group": "player",
|
|
32506
32586
|
"signature": "readonly etag: string",
|
|
32507
32587
|
"documented": true,
|
|
32508
|
-
"summary": "This save's version, which changes whenever the save does.\n\nNOT something to pass back on a write —
|
|
32588
|
+
"summary": "This save's version, which changes whenever the save does.\n\nNOT something to pass back on a write — the SDK remembers the version of\neach slot you last loaded or wrote and sends it for you, so a write that\nwould replace a save you never saw is refused as `save_conflict`. This is\nhow a game holding a copy tells whether the stored save is still the one\nit has.",
|
|
32509
32589
|
"deprecated": null,
|
|
32510
32590
|
"params": [],
|
|
32511
32591
|
"returns": null,
|
|
@@ -32636,9 +32716,9 @@
|
|
|
32636
32716
|
"name": "reason",
|
|
32637
32717
|
"anchor": "gemsaveerror-reason",
|
|
32638
32718
|
"group": "errors",
|
|
32639
|
-
"signature": "readonly reason: \"upload_failed\" | \"download_failed\" | \"save_timeout\" | \"save_malformed\"",
|
|
32719
|
+
"signature": "readonly reason: \"upload_failed\" | \"download_failed\" | \"save_timeout\" | \"save_malformed\" | \"save_conflict\" | \"load_failed\"",
|
|
32640
32720
|
"documented": true,
|
|
32641
|
-
"summary": "Which part failed.\n\n- `upload_failed` — the bytes could not be written to storage.\n- `download_failed` — the stored bytes could not be fetched.\n- `save_timeout` — a hop did not finish in the time the call allows.\n- `save_malformed` — the answer, or the stored payload, was not what it\n claims to be.\n\n`save_timeout`, and a one-off `upload_failed` or `download_failed`, are\nworth one retry. `save_malformed` is not: the same bytes will fail the\nsame way.\n\nAn `upload_failed` that repeats on EVERY save, for every player, is not\ntransient either — it is a platform-side misconfiguration, and retrying\nis a loop rather than a recovery. See the saves guide.",
|
|
32721
|
+
"summary": "Which part failed.\n\n- `upload_failed` — the bytes could not be written to storage.\n- `download_failed` — the stored bytes could not be fetched.\n- `save_timeout` — a hop did not finish in the time the call allows.\n- `save_malformed` — the answer, or the stored payload, was not what it\n claims to be.\n- `save_conflict` — a write was refused because the stored save changed\n since this game last loaded or wrote it: another device or tab saved\n in between. Nothing was written.\n- `load_failed` — a write was refused before anything was sent, because\n the last load of this slot failed, so what the write would replace is\n unknown — it could be the player's real progress.\n\n`save_timeout`, and a one-off `upload_failed` or `download_failed`, are\nworth one retry. `save_malformed` is not: the same bytes will fail the\nsame way.\n\nNeither is `save_conflict` or `load_failed`: resending the same write\nfails the same way. Load the slot again and decide what to write from\nwhat is stored — merge, or ask the player. Pass `overwrite: true` only to\nreplace whatever is stored on purpose.\n\nAn `upload_failed` that repeats on EVERY save, for every player, is not\ntransient either — it is a platform-side misconfiguration, and retrying\nis a loop rather than a recovery. See the saves guide.\n\nA RATE LIMIT IS NOT ONE OF THESE. Refused for the rate on any leg — the\nplatform, the upload, the download — a save or load rejects as\n`GemApiError` reason `rate_limited`, with `retryAfterSeconds` when a wait\nwas named.",
|
|
32642
32722
|
"deprecated": null,
|
|
32643
32723
|
"params": [],
|
|
32644
32724
|
"returns": null,
|
|
@@ -32691,7 +32771,7 @@
|
|
|
32691
32771
|
"group": "player",
|
|
32692
32772
|
"signature": "readonly signal?: AbortSignal",
|
|
32693
32773
|
"documented": true,
|
|
32694
|
-
"summary": "Cancel the write.\n\nA save is several hops, so a cancelled write can land after the platform\nhas already agreed to it. Re-read rather than assuming nothing was stored.",
|
|
32774
|
+
"summary": "Cancel the write.\n\nA save is several hops, so a cancelled write can land after the platform\nhas already agreed to it. Re-read rather than assuming nothing was stored.\n\nA write that replaced earlier ones still waiting on its slot carries them,\nso cancelling it before it goes out cancels them too. Cancelling a write\nthat a later one replaced rejects only this call; the later write goes on.",
|
|
32695
32775
|
"deprecated": null,
|
|
32696
32776
|
"params": [],
|
|
32697
32777
|
"returns": null,
|
|
@@ -32707,7 +32787,39 @@
|
|
|
32707
32787
|
"group": "player",
|
|
32708
32788
|
"signature": "readonly timeoutMs?: number",
|
|
32709
32789
|
"documented": true,
|
|
32710
|
-
"summary": "How long this whole save may take, every hop together. Overrides the 30\nsecond default.\n\nSET THIS WHEN YOU ARE RACING A CLOCK YOU DO NOT CONTROL — above all when\nsaving from `GemTerminationNotice`, whose `graceMs` can be shorter\nthan the default. Left at the default in that case a save is killed\nmid-flight: the promise never settles, nothing is logged, and the player\nloses progress silently. Set it to the budget you actually have and a save\nthat will not make it rejects as `save_timeout` while there is still time\nto do something about it.\n\nIt does not make a save faster. Three round trips plus the payload is what\na save costs; this only decides when to stop waiting.",
|
|
32790
|
+
"summary": "How long this whole save may take, every hop together. Overrides the 30\nsecond default.\n\nSET THIS WHEN YOU ARE RACING A CLOCK YOU DO NOT CONTROL — above all when\nsaving from `GemTerminationNotice`, whose `graceMs` can be shorter\nthan the default. Left at the default in that case a save is killed\nmid-flight: the promise never settles, nothing is logged, and the player\nloses progress silently. Set it to the budget you actually have and a save\nthat will not make it rejects as `save_timeout` while there is still time\nto do something about it.\n\nIt does not make a save faster. Three round trips plus the payload is what\na save costs; this only decides when to stop waiting.\n\nIt counts from the call, so a write waiting for the one before it on the\nsame slot spends it waiting. A rate limit whose wait it will not cover\nrejects at once as `GemApiError` reason `rate_limited`, not as\n`save_timeout`.\n\nIt stays this call's when a later write replaces this one: running out\nrejects only this call, and the later write goes on. The write that is\nsent runs on the newest call's `timeoutMs`, and running out of that rejects\nevery call it carries.",
|
|
32791
|
+
"deprecated": null,
|
|
32792
|
+
"params": [],
|
|
32793
|
+
"returns": null,
|
|
32794
|
+
"throws": null,
|
|
32795
|
+
"example": null,
|
|
32796
|
+
"defaultValue": null,
|
|
32797
|
+
"guides": []
|
|
32798
|
+
},
|
|
32799
|
+
{
|
|
32800
|
+
"id": "PutSaveOptions.teardown",
|
|
32801
|
+
"name": "teardown",
|
|
32802
|
+
"anchor": "putsaveoptions-teardown",
|
|
32803
|
+
"group": "player",
|
|
32804
|
+
"signature": "readonly teardown?: boolean",
|
|
32805
|
+
"documented": true,
|
|
32806
|
+
"summary": "This is the last write before the page goes away: send it without waiting\nfor the spacing between writes to one slot.\n\nWrites to one slot go out a few seconds apart, and a write made sooner\nwaits its turn. Set this on the write you make from\n`GemTerminationNotice` or `pagehide`, with a `timeoutMs` inside the\ntime you have.\n\nIT STILL WAITS FOR A WRITE ALREADY IN FLIGHT to the same slot, and it\nstill honours a rate-limit hold: when the hold outlasts `timeoutMs` the\nwrite rejects at once as `rate_limited`, and nothing is sent. Not for\nordinary saves — the spacing is what keeps a save loop from spending the\nrate limit.\n\nA later write that replaces this one while it waits keeps it: the write\nthat goes out skips the spacing when any call it carries set `teardown`.\nNot across a player change: a call replaced after the signed-in player\nchanged is refused as `save_conflict`, and its `teardown` goes with it.",
|
|
32807
|
+
"deprecated": null,
|
|
32808
|
+
"params": [],
|
|
32809
|
+
"returns": null,
|
|
32810
|
+
"throws": null,
|
|
32811
|
+
"example": null,
|
|
32812
|
+
"defaultValue": null,
|
|
32813
|
+
"guides": []
|
|
32814
|
+
},
|
|
32815
|
+
{
|
|
32816
|
+
"id": "PutSaveOptions.overwrite",
|
|
32817
|
+
"name": "overwrite",
|
|
32818
|
+
"anchor": "putsaveoptions-overwrite",
|
|
32819
|
+
"group": "player",
|
|
32820
|
+
"signature": "readonly overwrite?: boolean",
|
|
32821
|
+
"documented": true,
|
|
32822
|
+
"summary": "Write even if it replaces a save this game has not loaded.\n\nWithout it, a write to a slot whose last load FAILED is refused as\n`load_failed`, and a write is refused as `save_conflict` when the stored\nsave changed since you last loaded or wrote it. Both refusals protect the\nplayer's real progress from a game that started from defaults.\n\nSet it only when replacing whatever is stored is what you mean — a \"new\ngame\" the player confirmed, or the player choosing this device's progress\nafter a conflict. Never set it to get past a failed load.\n\nA later write that replaces this one while it waits keeps it: the write\nthat goes out overwrites when any call it carries set `overwrite`. A call\nthat gives up first (its `signal` or `timeoutMs`) takes it back, and so\ndoes one replaced after the signed-in player changed: it is refused as\n`save_conflict`, so one player's \"new game\" never overwrites the next's.",
|
|
32711
32823
|
"deprecated": null,
|
|
32712
32824
|
"params": [],
|
|
32713
32825
|
"returns": null,
|
|
@@ -32760,7 +32872,7 @@
|
|
|
32760
32872
|
"group": "player",
|
|
32761
32873
|
"signature": "readonly signal?: AbortSignal",
|
|
32762
32874
|
"documented": true,
|
|
32763
|
-
"summary": "Cancel the write.\n\nA save is several hops, so a cancelled write can land after the platform\nhas already agreed to it. Re-read rather than assuming nothing was stored.",
|
|
32875
|
+
"summary": "Cancel the write.\n\nA save is several hops, so a cancelled write can land after the platform\nhas already agreed to it. Re-read rather than assuming nothing was stored.\n\nA write that replaced earlier ones still waiting on its slot carries them,\nso cancelling it before it goes out cancels them too. Cancelling a write\nthat a later one replaced rejects only this call; the later write goes on.",
|
|
32764
32876
|
"deprecated": null,
|
|
32765
32877
|
"params": [],
|
|
32766
32878
|
"returns": null,
|
|
@@ -32776,7 +32888,39 @@
|
|
|
32776
32888
|
"group": "player",
|
|
32777
32889
|
"signature": "readonly timeoutMs?: number",
|
|
32778
32890
|
"documented": true,
|
|
32779
|
-
"summary": "How long this whole save may take, every hop together. Overrides the 30\nsecond default.\n\nSET THIS WHEN YOU ARE RACING A CLOCK YOU DO NOT CONTROL — above all when\nsaving from `GemTerminationNotice`, whose `graceMs` can be shorter\nthan the default. Left at the default in that case a save is killed\nmid-flight: the promise never settles, nothing is logged, and the player\nloses progress silently. Set it to the budget you actually have and a save\nthat will not make it rejects as `save_timeout` while there is still time\nto do something about it.\n\nIt does not make a save faster. Three round trips plus the payload is what\na save costs; this only decides when to stop waiting.",
|
|
32891
|
+
"summary": "How long this whole save may take, every hop together. Overrides the 30\nsecond default.\n\nSET THIS WHEN YOU ARE RACING A CLOCK YOU DO NOT CONTROL — above all when\nsaving from `GemTerminationNotice`, whose `graceMs` can be shorter\nthan the default. Left at the default in that case a save is killed\nmid-flight: the promise never settles, nothing is logged, and the player\nloses progress silently. Set it to the budget you actually have and a save\nthat will not make it rejects as `save_timeout` while there is still time\nto do something about it.\n\nIt does not make a save faster. Three round trips plus the payload is what\na save costs; this only decides when to stop waiting.\n\nIt counts from the call, so a write waiting for the one before it on the\nsame slot spends it waiting. A rate limit whose wait it will not cover\nrejects at once as `GemApiError` reason `rate_limited`, not as\n`save_timeout`.\n\nIt stays this call's when a later write replaces this one: running out\nrejects only this call, and the later write goes on. The write that is\nsent runs on the newest call's `timeoutMs`, and running out of that rejects\nevery call it carries.",
|
|
32892
|
+
"deprecated": null,
|
|
32893
|
+
"params": [],
|
|
32894
|
+
"returns": null,
|
|
32895
|
+
"throws": null,
|
|
32896
|
+
"example": null,
|
|
32897
|
+
"defaultValue": null,
|
|
32898
|
+
"guides": []
|
|
32899
|
+
},
|
|
32900
|
+
{
|
|
32901
|
+
"id": "PutBytesOptions.teardown",
|
|
32902
|
+
"name": "teardown",
|
|
32903
|
+
"anchor": "putbytesoptions-teardown",
|
|
32904
|
+
"group": "player",
|
|
32905
|
+
"signature": "readonly teardown?: boolean",
|
|
32906
|
+
"documented": true,
|
|
32907
|
+
"summary": "This is the last write before the page goes away: send it without waiting\nfor the spacing between writes to one slot.\n\nWrites to one slot go out a few seconds apart, and a write made sooner\nwaits its turn. Set this on the write you make from\n`GemTerminationNotice` or `pagehide`, with a `timeoutMs` inside the\ntime you have.\n\nIT STILL WAITS FOR A WRITE ALREADY IN FLIGHT to the same slot, and it\nstill honours a rate-limit hold: when the hold outlasts `timeoutMs` the\nwrite rejects at once as `rate_limited`, and nothing is sent. Not for\nordinary saves — the spacing is what keeps a save loop from spending the\nrate limit.\n\nA later write that replaces this one while it waits keeps it: the write\nthat goes out skips the spacing when any call it carries set `teardown`.\nNot across a player change: a call replaced after the signed-in player\nchanged is refused as `save_conflict`, and its `teardown` goes with it.",
|
|
32908
|
+
"deprecated": null,
|
|
32909
|
+
"params": [],
|
|
32910
|
+
"returns": null,
|
|
32911
|
+
"throws": null,
|
|
32912
|
+
"example": null,
|
|
32913
|
+
"defaultValue": null,
|
|
32914
|
+
"guides": []
|
|
32915
|
+
},
|
|
32916
|
+
{
|
|
32917
|
+
"id": "PutBytesOptions.overwrite",
|
|
32918
|
+
"name": "overwrite",
|
|
32919
|
+
"anchor": "putbytesoptions-overwrite",
|
|
32920
|
+
"group": "player",
|
|
32921
|
+
"signature": "readonly overwrite?: boolean",
|
|
32922
|
+
"documented": true,
|
|
32923
|
+
"summary": "Write even if it replaces a save this game has not loaded.\n\nWithout it, a write to a slot whose last load FAILED is refused as\n`load_failed`, and a write is refused as `save_conflict` when the stored\nsave changed since you last loaded or wrote it. Both refusals protect the\nplayer's real progress from a game that started from defaults.\n\nSet it only when replacing whatever is stored is what you mean — a \"new\ngame\" the player confirmed, or the player choosing this device's progress\nafter a conflict. Never set it to get past a failed load.\n\nA later write that replaces this one while it waits keeps it: the write\nthat goes out overwrites when any call it carries set `overwrite`. A call\nthat gives up first (its `signal` or `timeoutMs`) takes it back, and so\ndoes one replaced after the signed-in player changed: it is refused as\n`save_conflict`, so one player's \"new game\" never overwrites the next's.",
|
|
32780
32924
|
"deprecated": null,
|
|
32781
32925
|
"params": [],
|
|
32782
32926
|
"returns": null,
|
|
@@ -32808,7 +32952,7 @@
|
|
|
32808
32952
|
"group": "player",
|
|
32809
32953
|
"signature": "class SavesApi",
|
|
32810
32954
|
"documented": true,
|
|
32811
|
-
"summary": "A player's saved progress — write it, read it, list it, delete it.\n\nReached as `gem.me.saves`, and on a character as\n`gem.characters.get(id).saves`.\n\nA SAVE GOES IN A SLOT YOU DECLARED. Slots live in your game config, published\nwith `gemctl`, next to your room templates. Writing to a slot that was never\ndeclared fails as `not_found` naming the key, on the first call and every\nretry — that is the wall almost everyone hits first, and it is a config\nproblem rather than a code one.\n\n```js\nawait gem.me.saves.put('slot1', { level: 7, checkpoint: 'bridge' });\nconst state = await gem.me.saves.get('slot1');\n```\n\nWHAT IS DELIBERATELY NOT HERE: no autosave timer, no save format, no\nmigration. Those are your game's, and a platform that guessed at them would\nguess wrong.",
|
|
32955
|
+
"summary": "A player's saved progress — write it, read it, list it, delete it.\n\nReached as `gem.me.saves`, and on a character as\n`gem.characters.get(id).saves`.\n\nA SAVE GOES IN A SLOT YOU DECLARED. Slots live in your game config, published\nwith `gemctl`, next to your room templates. Writing to a slot that was never\ndeclared fails as `not_found` naming the key, on the first call and every\nretry — that is the wall almost everyone hits first, and it is a config\nproblem rather than a code one.\n\n```js\nawait gem.me.saves.put('slot1', { level: 7, checkpoint: 'bridge' });\nconst state = await gem.me.saves.get('slot1');\n```\n\nLOAD BEFORE YOU SAVE. Each write carries the version of the slot your last\nload or write found, so a write that would replace a save this game has not\nseen — another device's, or the player's real progress after a load that\nfailed — is refused as `save_conflict` or `load_failed` rather than stored.\nAfter a failed load, play on without saving and load again later; pass\n`overwrite: true` only when the player chose to start over.\n\nONE WRITE PER SLOT AT A TIME. A write made while another to the same slot is\nin flight waits for it, and a later one replaces it: the newest value is the\none sent, and every caller it replaced resolves with it, keeping its own\n`signal`, `timeoutMs`, `teardown` and `overwrite`. Writes to one slot\ngo out a few seconds apart (`teardown: true` skips that), and after a\n`rate_limited` answer none goes out until the wait it named is over.\n\nWHAT IS DELIBERATELY NOT HERE: no autosave timer, no save format, no\nmigration. Those are your game's, and a platform that guessed at them would\nguess wrong.",
|
|
32812
32956
|
"deprecated": null,
|
|
32813
32957
|
"params": [],
|
|
32814
32958
|
"returns": null,
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,36 @@ 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
|
+
## 16.2.0
|
|
20
|
+
|
|
21
|
+
Upgrading from 16.1.0. Impact: required.
|
|
22
|
+
|
|
23
|
+
### Must change
|
|
24
|
+
|
|
25
|
+
- The reason on a save error can now also be save_conflict or load_failed. Any code that switches over the reason exhaustively, or maps it to a message, will stop compiling or fall through, so handle both new values. save_conflict means the write was refused because the saved data changed underneath it, or because the player changed while the write was waiting. Reload the save and decide whether to retry, or pass overwrite when replacing the stored data is intended. load_failed means a save could not be loaded.
|
|
26
|
+
|
|
27
|
+
### Rebuild
|
|
28
|
+
|
|
29
|
+
- The save error constructor now accepts the two new reasons, and the saves API constructor takes an optional trailing identity function. Recompiling is enough; existing calls stay valid.
|
|
30
|
+
|
|
31
|
+
### New capability
|
|
32
|
+
|
|
33
|
+
- Save and byte writes accept an overwrite option. Set it when a write should replace whatever is stored without the conflict check; otherwise a write that collides with newer saved data is refused as save_conflict.
|
|
34
|
+
- Save and byte writes accept a teardown option. Set it on a final write, such as when the game is closing, so the write skips the spacing between writes and goes out right away.
|
|
35
|
+
- The saves options accept minWriteIntervalMs, which sets the minimum spacing between writes. Tune it if the default spacing does not suit how often your game saves.
|
|
36
|
+
|
|
37
|
+
## 16.1.0
|
|
38
|
+
|
|
39
|
+
Upgrading from 16.0.1. Impact: required.
|
|
40
|
+
|
|
41
|
+
### Rebuild
|
|
42
|
+
|
|
43
|
+
- The signalMoment method on the frame now accepts an optional fourth parameter, wall, in the game, root and SDK entry points. Existing calls with three arguments keep working unchanged, so recompiling your game against the new types is enough.
|
|
44
|
+
|
|
45
|
+
### New capability
|
|
46
|
+
|
|
47
|
+
- A game can now mark a platform moment with wall set to true to say it accepts that the Arcade may show its app wall at that break. Leaving it out or passing false keeps the break ordinary. An older Arcade ignores the field, so it is safe to send everywhere. Only set it where showing the wall at that break is acceptable for your game.
|
|
48
|
+
|
|
19
49
|
## 16.0.1
|
|
20
50
|
|
|
21
51
|
Upgrading from 16.0.0. Impact: none.
|
package/docs/getting-started.md
CHANGED
package/docs/manifest.json
CHANGED
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"file": "changelog.md",
|
|
46
|
-
"sha256": "
|
|
47
|
-
"updated": "2026-10-
|
|
46
|
+
"sha256": "b2791239b695b7830c97ccb7ef1370c317b92070a9a3f17d8488d02d78965094",
|
|
47
|
+
"updated": "2026-10-29T20:57:56Z"
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"file": "daily-challenges.md",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"file": "getting-started.md",
|
|
76
|
-
"sha256": "
|
|
77
|
-
"updated": "2026-10-
|
|
76
|
+
"sha256": "0699d1d9283ec251416a8068a6d1dfa050d795c58c55656cc5ce83f98e5bf132",
|
|
77
|
+
"updated": "2026-10-29T20:57:56Z"
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"file": "inventory-and-catalogs.md",
|
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"file": "platform-moments.md",
|
|
101
|
-
"sha256": "
|
|
102
|
-
"updated": "2026-10-
|
|
101
|
+
"sha256": "2d7a24af647c6820fb6d4b098ce417ca81ceaf131bf4c02a51f3e82e45631e01",
|
|
102
|
+
"updated": "2026-10-29T16:05:50Z"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
"file": "platform-rules.md",
|
|
@@ -128,13 +128,13 @@
|
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"file": "saving-player-progress.md",
|
|
131
|
-
"sha256": "
|
|
132
|
-
"updated": "2026-10-
|
|
131
|
+
"sha256": "78210e1fec9590a0b8051c16d6adcf685391aa32175d8abe80614f840358c7ec",
|
|
132
|
+
"updated": "2026-10-29T17:51:52Z"
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
"file": "server-bundles.md",
|
|
136
|
-
"sha256": "
|
|
137
|
-
"updated": "2026-10-
|
|
136
|
+
"sha256": "3d628942136f6dbf2dc0dd25a825f4520fe9b54ffc88a62e2d9fed11039a5151",
|
|
137
|
+
"updated": "2026-10-29T20:57:56Z"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"file": "serverless-functions-js.md",
|
package/docs/platform-moments.md
CHANGED
|
@@ -30,6 +30,7 @@ put the call, where never to, and the few things a game gets wrong when nobody t
|
|
|
30
30
|
- [10. Testing it in the dev host](#10-testing-it-in-the-dev-host)
|
|
31
31
|
- [11. The release requirement](#11-the-release-requirement)
|
|
32
32
|
- [12. Moving off `format: 'interstitial'`](#12-moving-off-format-interstitial)
|
|
33
|
+
- [13. A break that may be a wall: `wall: true`](#13-a-break-that-may-be-a-wall-wall-true)
|
|
33
34
|
|
|
34
35
|
## 1. A platform moment is not your own offer
|
|
35
36
|
|
|
@@ -133,7 +134,7 @@ you find the mistake while you build:
|
|
|
133
134
|
| token | when |
|
|
134
135
|
| --------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
135
136
|
| `moments_unsupported` | the page your game runs in does not do platform moments. Keep the call; it costs nothing. Said once a session |
|
|
136
|
-
| `moments_invalid_argument` | a `kind` outside the five,
|
|
137
|
+
| `moments_invalid_argument` | a `kind` outside the five, a `placement` outside the grammar, or a `wall` that is not a boolean |
|
|
137
138
|
| `moments_before_ready` | a signal before `gem.frame.ready()` — play has not started, so there is no break |
|
|
138
139
|
| `moments_in_flight` | a second signal while the first is still waiting for its answer |
|
|
139
140
|
| `moments_called_repeatedly` | a signal within 10 seconds of the last moment's answer — one break signalled twice, or a timer |
|
|
@@ -264,3 +265,33 @@ await gem.moments.signal({ kind: 'level_end', placement: 'level-complete' });
|
|
|
264
265
|
|
|
265
266
|
`format: 'rewarded'` is not deprecated and does not change: a button the player presses for a
|
|
266
267
|
reward is your game's own offer, and [`rewarded-ads.md`](rewarded-ads.md) is its page.
|
|
268
|
+
|
|
269
|
+
## 13. A break that may be a wall: `wall: true`
|
|
270
|
+
|
|
271
|
+
`wall` is optional. Set it to `true` at a break where your game **accepts that the Arcade may show
|
|
272
|
+
its app wall**: a panel that asks the player to carry on in the Gem Arcade app, and that may leave
|
|
273
|
+
them no way to keep playing in this browser.
|
|
274
|
+
|
|
275
|
+
```js
|
|
276
|
+
// From the Next press, after the player's progress is saved.
|
|
277
|
+
await saveProgress();
|
|
278
|
+
await gem.moments.signal({ kind: 'level_end', placement: 'chapter-1-end', wall: true });
|
|
279
|
+
startNextLevel();
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- **Absent or `false` is an ordinary break**, exactly as before the flag existed. Leave it off
|
|
283
|
+
everywhere you would not want a player to stop.
|
|
284
|
+
- **Only where stopping is acceptable, and only after saving.** The end of a level, a chapter or a
|
|
285
|
+
run, once the player's progress is saved where it follows them
|
|
286
|
+
([`saving-player-progress.md`](saving-player-progress.md)) — never mid-run, and never at a break
|
|
287
|
+
whose progress is lost if the player does not come back to this tab.
|
|
288
|
+
- **An acceptance, not an ask.** The Arcade still decides, on its own servers and under its own
|
|
289
|
+
settings, whether to show anything at that break, and usually shows nothing. The flag only
|
|
290
|
+
widens what it may show over your game; it never makes anything appear.
|
|
291
|
+
- **Nothing else changes.** Call it from the same press, once per break (§3). The answer is the
|
|
292
|
+
same `{ shown }`, it grants nothing (§6), and the Arcade pauses your game the same way while
|
|
293
|
+
anything is up (§7). If a wall is shown, your game stays paused behind it.
|
|
294
|
+
- **A `wall` that is not a boolean** is refused like any other bad argument: `{ shown: false }`
|
|
295
|
+
and a `moments_invalid_argument` report (§5).
|
|
296
|
+
- **An older Arcade ignores it** and treats the break as ordinary, so the flag is safe to send
|
|
297
|
+
everywhere. The dev host's log line (§10) does not carry it.
|