@indiegems/gem-web-sdk 7.8.0 → 7.9.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 +1 -1
- package/dist/game.d.ts +22 -1
- package/dist/gem-web-sdk.bridge.js +1 -1
- package/dist/gem-web-sdk.esm.js +2 -2
- package/dist/gem-web-sdk.function.js +2 -2
- package/dist/gem-web-sdk.game.js +2 -2
- package/dist/gem-web-sdk.min.js +2 -2
- package/dist/gem-web-sdk.sdk.js +2 -2
- package/dist/gem-web-sdk.server.js +2 -2
- package/dist/index.d.ts +32 -4
- package/dist/sdk.d.ts +22 -1
- package/docs/achievements.md +99 -3
- package/docs/api/api-reference-game.md +158 -9
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +177 -4
- package/docs/changelog.md +1 -1
- package/docs/daily-challenges.md +9 -8
- package/docs/game-economy-tutorial.md +3 -2
- package/docs/getting-started.md +8 -4
- package/docs/manifest.json +14 -14
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +116 -38
- package/package.json +1 -1
- package/schema/bridge.v1.json +104 -2
- package/schema/capabilities.public.json +7 -0
package/docs/getting-started.md
CHANGED
|
@@ -50,7 +50,7 @@ either way.
|
|
|
50
50
|
```jsonc
|
|
51
51
|
// package.json
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@indiegems/gem-web-sdk": "7.
|
|
53
|
+
"@indiegems/gem-web-sdk": "7.9.0"
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -175,7 +175,10 @@ gem.frame.progress(loaded, total); // while assets load
|
|
|
175
175
|
gem.frame.ready(); // once the player can actually play
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
And
|
|
178
|
+
And tell the Arcade where your settings are — `gem.setMenu({ onSettings })` puts them in the
|
|
179
|
+
Arcade's own menu, top-right, beside Return to the Arcade
|
|
180
|
+
([what a game must do](what-a-game-must-do.md#the-arcades-menu-your-settings-live-in-it)). Outside
|
|
181
|
+
the Arcade there is no such menu, so also give players a way out — a control that calls `gem.exit()`. A game a player
|
|
179
182
|
cannot leave is one they leave by closing the tab.
|
|
180
183
|
|
|
181
184
|
## 6. Play it
|
|
@@ -347,8 +350,9 @@ What reaches the frame:
|
|
|
347
350
|
- **The query only.** A fragment (`#…`) is never forwarded.
|
|
348
351
|
- **Not the arcade's own keys.** These are stripped and never reach the game, and they are the
|
|
349
352
|
only keys the arcade keeps for itself; it never interprets yours:
|
|
350
|
-
- Store and play-page switches: `play`, `channel`, `purchase
|
|
351
|
-
|
|
353
|
+
- Store and play-page switches: `play`, `channel`, `purchase`, and `build`, which pins one
|
|
354
|
+
of your own builds when you play it from your Builds page.
|
|
355
|
+
- Platform switches: `devtools`, `devLocale`.
|
|
352
356
|
- The arcade's own sign-in return marker: `social`.
|
|
353
357
|
- Attribution and campaign keys, which the arcade keeps out of every game: `_s`, `utm_*`
|
|
354
358
|
(every key with that prefix, matched case-sensitively, so UTM_X and utmost are yours),
|
package/docs/manifest.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"documents": [
|
|
9
9
|
{
|
|
10
10
|
"file": "achievements.md",
|
|
11
|
-
"sha256": "
|
|
12
|
-
"updated": "2026-
|
|
11
|
+
"sha256": "e67bc5d0e6abd9ff842be46fa82bf5117fce74b2eb9d28a3954919ddc8a11c73",
|
|
12
|
+
"updated": "2026-10-02T23:09:37Z"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"file": "analytics.md",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"file": "changelog.md",
|
|
36
|
-
"sha256": "
|
|
37
|
-
"updated": "2026-10-
|
|
36
|
+
"sha256": "3398bf6f55c503520167dc9cfbee06da7f0552125a4b2050d7e508096c8e256f",
|
|
37
|
+
"updated": "2026-10-02T21:36:48Z"
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"file": "cross-promotion.md",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"file": "daily-challenges.md",
|
|
46
|
-
"sha256": "
|
|
47
|
-
"updated": "2026-10-
|
|
46
|
+
"sha256": "c198868e2cb1f1b5d9164bb5b1d23bf3f502987e45e10163c8b048a8eba24667",
|
|
47
|
+
"updated": "2026-10-02T23:01:00Z"
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"file": "dedicated-server-flow.md",
|
|
@@ -63,13 +63,13 @@
|
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"file": "game-economy-tutorial.md",
|
|
66
|
-
"sha256": "
|
|
67
|
-
"updated": "2026-10-
|
|
66
|
+
"sha256": "08588571cd6529d81bd064f65bc3158997418aeb88ce00ae281735c8b44bff1f",
|
|
67
|
+
"updated": "2026-10-02T19:05:02Z"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"file": "getting-started.md",
|
|
71
|
-
"sha256": "
|
|
72
|
-
"updated": "2026-10-
|
|
71
|
+
"sha256": "63a367fdcf79abee6a7978057cb1b7065f0365cd6fa5a895cbd2b18f76d29701",
|
|
72
|
+
"updated": "2026-10-03T22:38:04Z"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"file": "inventory-and-catalogs.md",
|
|
@@ -118,8 +118,8 @@
|
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"file": "server-bundles.md",
|
|
121
|
-
"sha256": "
|
|
122
|
-
"updated": "2026-10-
|
|
121
|
+
"sha256": "419a1587827f494f638f331571a6385f4de080efb062e83656a9d2c50ee3473b",
|
|
122
|
+
"updated": "2026-10-03T22:38:04Z"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"file": "serverless-functions-js.md",
|
|
@@ -138,8 +138,8 @@
|
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"file": "what-a-game-must-do.md",
|
|
141
|
-
"sha256": "
|
|
142
|
-
"updated": "2026-10-
|
|
141
|
+
"sha256": "e5d0e2622077d4e41d1f1786c834e6280356f525146193285d062c4209ac33bd",
|
|
142
|
+
"updated": "2026-10-03T22:11:20Z"
|
|
143
143
|
}
|
|
144
144
|
]
|
|
145
145
|
}
|
package/docs/server-bundles.md
CHANGED
|
@@ -80,8 +80,8 @@ To check the download yourself: the package npm serves is, byte for byte, the fi
|
|
|
80
80
|
also publishes on its download host, with the hash beside it:
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
curl -fsSL -O https://downloads.gemarcade.com/sdk/v7.
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/v7.
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v7.9.0/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v7.9.0/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -353,6 +353,33 @@ one refused orientation, in fullscreen, once they are already the right way roun
|
|
|
353
353
|
That is best effort (iOS does not implement it, and the native app's webview declines
|
|
354
354
|
it), which is why the panel exists and why it always offers a way past.
|
|
355
355
|
|
|
356
|
+
## An achievement unlock — celebrate it yourself, or declare the Arcade's
|
|
357
|
+
|
|
358
|
+
By default the Arcade draws no card when a player unlocks one of your game's
|
|
359
|
+
achievements: **the celebration is your game's**, in its own art and sound. If your
|
|
360
|
+
game has no celebration of its own, declare the Arcade's card on your store page
|
|
361
|
+
instead:
|
|
362
|
+
|
|
363
|
+
```yaml
|
|
364
|
+
unlock_celebration: arcade
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
`game` is the default, and it is what an absent line means.
|
|
368
|
+
|
|
369
|
+
- **Opt in when** your game has no unlock celebration of its own, so its players
|
|
370
|
+
still see their unlocks.
|
|
371
|
+
- **Do not opt in when** your game draws its own. The Arcade's card then lands on top
|
|
372
|
+
of yours, and the player sees two.
|
|
373
|
+
- **Platform milestones** — the Achievement Score tiers, which belong to no game —
|
|
374
|
+
always get the Arcade's card, whatever you declare.
|
|
375
|
+
- **Only the store page on `release` is read**, and it takes effect when that page is
|
|
376
|
+
promoted. It is display-only, and it needs no `schema_version` change.
|
|
377
|
+
- **Gems or Tickets paid in the same moment** still go through `gem.cueReward`, with
|
|
378
|
+
no flight of your own.
|
|
379
|
+
|
|
380
|
+
How your game learns an unlock happened, and the field's full rules, are in
|
|
381
|
+
[achievements.md §5](achievements.md#5-celebrating-an-unlock-is-your-games-job).
|
|
382
|
+
|
|
356
383
|
## A television, if a player puts you on one
|
|
357
384
|
|
|
358
385
|
The Arcade runs in TV browsers and on TV hardware, so a game can find itself ten feet from its
|
|
@@ -435,7 +462,9 @@ there is no route by which the frame could request one. `createGem` forwards all
|
|
|
435
462
|
arcade cannot release yours from outside the frame. Keep it captured and the player cannot click
|
|
436
463
|
the dialog sitting on top of your game.
|
|
437
464
|
|
|
438
|
-
**Save on pause.** When
|
|
465
|
+
**Save on pause.** When a player on a phone chooses Return to the Arcade in
|
|
466
|
+
[the Arcade's menu](#the-arcades-menu-your-settings-live-in-it), or presses the device's Back, your
|
|
467
|
+
game is **parked**, not closed:
|
|
439
468
|
it receives `onPause(true)` and stays loaded while the player browses. They may come back and
|
|
440
469
|
press Continue — `onPause(false)`, and play resumes where it stopped — or the parked game may be
|
|
441
470
|
ended up to five minutes later. That end usually comes with `onWillTerminate` first, **but not
|
|
@@ -542,17 +571,20 @@ the `var()` above, or read `gem.viewport`. The release checklist looks for the b
|
|
|
542
571
|
|
|
543
572
|
### The arcade's own controls: reserved areas
|
|
544
573
|
|
|
545
|
-
On a phone your game is full-bleed, and the arcade keeps one
|
|
546
|
-
|
|
574
|
+
On a phone your game is full-bleed, and the arcade keeps one cluster of its own over it, in the
|
|
575
|
+
**top-right corner**:
|
|
576
|
+
|
|
577
|
+
- **The menu button**, in the corner itself, always there. It opens
|
|
578
|
+
[the Arcade's menu](#the-arcades-menu-your-settings-live-in-it): Return to the Arcade, your
|
|
579
|
+
settings, mute and the rest.
|
|
580
|
+
- **The player's wallets** (Gems, Tickets, their achievement score), to the left of it, while your
|
|
581
|
+
screen [shows them](#the-arcades-wallet-show-it-never-draw-it). When gameplay hides them with
|
|
582
|
+
`gem.setWalletVisible(false)`, the cluster shrinks to the menu button alone.
|
|
547
583
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
[shows them](#the-arcades-wallet-show-it-never-draw-it). Put your own settings, pause or menu
|
|
553
|
-
button top-right **below** them, at `top: var(--gem-reserved-tr-height, …)` — and when gameplay
|
|
554
|
-
hides the wallets with `gem.setWalletVisible(false)`, that rectangle shrinks to nothing and your
|
|
555
|
-
button moves up into the corner on its own.
|
|
584
|
+
**The top-left corner is yours.** Put your own back or home button there — the one that moves around
|
|
585
|
+
_inside_ your game, back to your level select or your title screen. **Put no settings, pause-menu or
|
|
586
|
+
burger button of your own top-right**: your settings open from the Arcade's menu, and a second
|
|
587
|
+
button under the Arcade's would mean something else in every game.
|
|
556
588
|
|
|
557
589
|
Those rectangles of your frame are **reserved**: keep essential UI and every touch target out of
|
|
558
590
|
them. **Never hard-code their size or height.** They differ by phone, and in native fullscreen the
|
|
@@ -575,17 +607,17 @@ from its top-left corner:
|
|
|
575
607
|
reserved rectangles reach in from its two edges. Push the corner's UI clear of it:
|
|
576
608
|
|
|
577
609
|
```css
|
|
578
|
-
/*
|
|
579
|
-
#
|
|
580
|
-
top:
|
|
581
|
-
|
|
610
|
+
/* your back button in the top-left corner, which the arcade leaves clear */
|
|
611
|
+
#back {
|
|
612
|
+
top: var(--gem-safe-top, env(safe-area-inset-top, 0px));
|
|
613
|
+
left: var(--gem-safe-left, 0px);
|
|
582
614
|
}
|
|
583
|
-
/* a score in the top-
|
|
615
|
+
/* a score in the top-right corner, below the arcade's cluster */
|
|
584
616
|
#score {
|
|
585
|
-
top: max(var(--gem-safe-top, env(safe-area-inset-top, 0px)), var(--gem-reserved-
|
|
586
|
-
|
|
617
|
+
top: max(var(--gem-safe-top, env(safe-area-inset-top, 0px)), var(--gem-reserved-tr-height, 0px));
|
|
618
|
+
right: var(--gem-safe-right, 0px);
|
|
587
619
|
}
|
|
588
|
-
/* or a row along the top,
|
|
620
|
+
/* or a row along the top, stopping short of it */
|
|
589
621
|
#top-bar {
|
|
590
622
|
padding-left: var(--gem-reserved-tl-width, 0px);
|
|
591
623
|
padding-right: var(--gem-reserved-tr-width, 0px);
|
|
@@ -597,13 +629,15 @@ value; they are never set under an embedder that sends no viewport, which is wha
|
|
|
597
629
|
fallback is for. A rectangle belongs to the corner its centre lies nearest to. For a HUD laid out
|
|
598
630
|
from script, read `gem.viewport.reserved` — `[]` when nothing is covered.
|
|
599
631
|
|
|
600
|
-
**Lay out against it whenever it changes.** Shown wallets
|
|
601
|
-
shrinks it to
|
|
602
|
-
|
|
632
|
+
**Lay out against it whenever it changes.** Shown wallets widen the top-right rectangle; hiding them
|
|
633
|
+
shrinks it to the menu button alone. A wallet the arcade pops up for a moment while they are hidden
|
|
634
|
+
never reserves anything — it floats over your game and moves nothing. Read every corner, not only the
|
|
635
|
+
one this page names today: `--gem-reserved-tl-*` is `0px` now, and a layout that honours it anyway
|
|
636
|
+
is right under any embedder.
|
|
603
637
|
|
|
604
638
|
**Older SDKs get it folded into the top inset.** A game on an SDK from before reserved areas cannot
|
|
605
639
|
read the rectangles, so the arcade sends it none and instead raises `insets.top` — and so
|
|
606
|
-
`--gem-safe-top` — past the bottom of the
|
|
640
|
+
`--gem-safe-top` — past the bottom of the cluster. That game keeps working, at the cost of a full-width
|
|
607
641
|
band; upgrading gives the band back.
|
|
608
642
|
|
|
609
643
|
## Which store-page experiment brought this player in: cohorts
|
|
@@ -695,18 +729,62 @@ and anything that must follow them, is a save or `gem.me.settings`:
|
|
|
695
729
|
draws the line in full. Never block your first frame on it either: apply defaults, then apply what
|
|
696
730
|
arrives.
|
|
697
731
|
|
|
732
|
+
## The Arcade's menu: your settings live in it
|
|
733
|
+
|
|
734
|
+
The Arcade keeps one menu button over every game, top-right, on every device. Its menu is how a
|
|
735
|
+
player leaves (**Return to the Arcade**), mutes, goes fullscreen and reports a problem — and how they
|
|
736
|
+
open **your settings**, once you say you have some:
|
|
737
|
+
|
|
738
|
+
```js
|
|
739
|
+
const inArcadeMenu = gem.setMenu({
|
|
740
|
+
onSettings: () => settingsScreen.open(),
|
|
741
|
+
items: [{ id: 'how-to-play', label: t('How to play'), onSelect: () => help.open() }],
|
|
742
|
+
});
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
- **`onSettings` puts a Settings entry in the menu.** The Arcade closes its menu, resumes your game,
|
|
746
|
+
and calls it; you draw your own settings screen, as you always did. Leave it out and the menu has
|
|
747
|
+
no Settings entry.
|
|
748
|
+
- **`items` are up to four entries of your own**, shown in a group below the Arcade's. `id` is your
|
|
749
|
+
name for the entry (1 to 32 characters); `label` is what the player reads, **already in their
|
|
750
|
+
language** (`gem.prefs.locale`) — the Arcade does not translate it, cuts it at 40 characters, and
|
|
751
|
+
may shorten it further. A fifth entry is left out.
|
|
752
|
+
- **It is screen state, not a toggle**, like the wallets: each call is the whole answer and replaces
|
|
753
|
+
the last. Call it again when the language changes or a screen has different options;
|
|
754
|
+
`gem.setMenu({})` takes everything of yours back out. Before the Arcade connects the menu is held
|
|
755
|
+
and sent when it does, and again after any reconnect.
|
|
756
|
+
- **The Arcade pauses your game while its menu is open** — `onPause(true)`, then `onPause(false)`
|
|
757
|
+
before a handler of yours runs. Nothing else is needed from you.
|
|
758
|
+
- **You cannot remove or reorder the Arcade's entries**, and Return to the Arcade is always there.
|
|
759
|
+
On a phone it **parks** your game, as above; on a desktop, a tablet and a television it ends the
|
|
760
|
+
session, with `onWillTerminate` first. Either way your game heard `onPause(true)` when the menu
|
|
761
|
+
opened: save there — it is the edge you are sure to get.
|
|
762
|
+
|
|
763
|
+
**Do not draw a settings button of your own while `gem.setMenu` returns `true`.** One menu, in one
|
|
764
|
+
place, in every game is the point. Draw your own **only when it returns `false` after the bridge has
|
|
765
|
+
settled** — an embedder with no menu, or your game running outside any embedder:
|
|
766
|
+
|
|
767
|
+
```js
|
|
768
|
+
await gem.awaitBridge();
|
|
769
|
+
ownSettingsButton.hidden = gem.setMenu({ onSettings: () => settingsScreen.open() });
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
Your own **back or home** button — the one that moves around inside your game — goes **top-left**,
|
|
773
|
+
which the Arcade [leaves clear](#the-arcades-own-controls-reserved-areas). It never leaves the game:
|
|
774
|
+
leaving is the Arcade's menu, so a player always knows which control does which.
|
|
775
|
+
|
|
698
776
|
## Sound: your own mute control, and the Arcade's
|
|
699
777
|
|
|
700
|
-
**Every game needs its own mute control**, in its settings
|
|
701
|
-
|
|
702
|
-
|
|
778
|
+
**Every game needs its own mute control**, in its settings screen. The Arcade's menu has a mute
|
|
779
|
+
entry on every device, but a player who opens your settings expects sound to be there too — and
|
|
780
|
+
under an embedder with no menu, yours is the only one there is.
|
|
703
781
|
|
|
704
782
|
Wire it both ways, so the player has one mute rather than two that disagree:
|
|
705
783
|
|
|
706
784
|
```js
|
|
707
785
|
const gem = createGem({
|
|
708
786
|
// ...
|
|
709
|
-
// The Arcade's mute changed
|
|
787
|
+
// The Arcade's mute changed: follow it.
|
|
710
788
|
onPrefs: (prefs) => sound.setMuted(prefs.muted),
|
|
711
789
|
});
|
|
712
790
|
|
|
@@ -726,14 +804,14 @@ sent when the Arcade connects; an embedder with no mute button is simply not tol
|
|
|
726
804
|
button saying one thing while the game does the other; a game that follows `prefs.muted` but never
|
|
727
805
|
reports leaves the Arcade's button stale after the player uses yours.
|
|
728
806
|
|
|
729
|
-
### The Arcade's mute
|
|
807
|
+
### The Arcade's mute entry
|
|
730
808
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
809
|
+
The Arcade's menu carries a mute entry on every device, for games whose SDK can honour it — an SDK
|
|
810
|
+
that silences your game says so when it connects, and a game built on an older one gets no entry
|
|
811
|
+
rather than one that does nothing. When the player presses it,
|
|
734
812
|
`gem.prefs.muted` arrives as `true` and the SDK silences your page itself — every Web Audio graph and
|
|
735
813
|
every `<audio>`/`<video>` element — then gives the sound back when they press it again. With the two
|
|
736
|
-
calls above, the Arcade's
|
|
814
|
+
calls above, the Arcade's entry and yours stay in step whichever the player uses.
|
|
737
815
|
|
|
738
816
|
- **It turns the volume down; it does not stop your clock.** Your `AudioContext` keeps running, so
|
|
739
817
|
anything scheduled against `currentTime` stays in time. Do not answer a mute with `ctx.suspend()` in
|
|
@@ -780,8 +858,8 @@ and the release checklist checks that it does.
|
|
|
780
858
|
|
|
781
859
|
The player's platform balances — Gems, Tickets (`ARCADE_TICKETS`) and their achievement score —
|
|
782
860
|
belong to the Arcade. **Your game never draws one**, and never shows a pending state for one: the
|
|
783
|
-
Arcade takes every figure from the platform and draws the wallets itself,
|
|
784
|
-
of your frame on a phone.
|
|
861
|
+
Arcade takes every figure from the platform and draws the wallets itself, beside its menu button in
|
|
862
|
+
the top-right corner of your frame on a phone.
|
|
785
863
|
|
|
786
864
|
**They are shown unless your game hides them.** A game that does nothing has every wallet on show
|
|
787
865
|
the whole time. The one rule: **hide them during gameplay, show them on your menus.**
|
|
@@ -797,10 +875,10 @@ pauseMenu.onOpen = () => gem.setWalletVisible(true);
|
|
|
797
875
|
- **It is screen state, not a toggle.** Each call is the whole answer and replaces the last. Call it
|
|
798
876
|
as often as you like: before the Arcade connects the state is held and sent when it does, and
|
|
799
877
|
again after any reconnect; a repeat of the current state is not re-sent.
|
|
800
|
-
- **Shown wallets take space; a pop does not.** Shown, they
|
|
801
|
-
[
|
|
802
|
-
`--gem-reserved-tr
|
|
803
|
-
|
|
878
|
+
- **Shown wallets take space; a pop does not.** Shown, they widen the top-right
|
|
879
|
+
[reserved area](#the-arcades-own-controls-reserved-areas) beside the menu button, so lay your
|
|
880
|
+
screen out against `--gem-reserved-tr-*`; hidden, it shrinks back to the button alone. A pop floats
|
|
881
|
+
over your game and never moves its layout.
|
|
804
882
|
- **Narrowing is optional.** On a screen that sells in one currency — a ticket store — you may show
|
|
805
883
|
only that one: `gem.setWalletVisible(true, { only: ['ARCADE_TICKETS'] })`, with `'score'` for the
|
|
806
884
|
achievement score. Use it only there; leaving it out shows every wallet, which is always safe.
|
package/package.json
CHANGED
package/schema/bridge.v1.json
CHANGED
|
@@ -893,7 +893,7 @@
|
|
|
893
893
|
"",
|
|
894
894
|
"AMENDED, DELIBERATELY: this message once carried insets only. It now also",
|
|
895
895
|
"carries `reserved`, rectangles of the frame covered by the parent's own",
|
|
896
|
-
"controls floating over the game (a
|
|
896
|
+
"controls floating over the game (a menu button in a corner, the wallet chips",
|
|
897
897
|
"shown in another). An inset can only describe a full-width or full-height band,",
|
|
898
898
|
"and a corner tab expressed as a top inset would take the whole top strip",
|
|
899
899
|
"from a full-bleed game to clear a control a few dozen pixels wide. A",
|
|
@@ -2387,7 +2387,7 @@
|
|
|
2387
2387
|
"to it: a reward cue, a pending grant, a landing, a spend, a daily-maximum",
|
|
2388
2388
|
"note, a score change. The game's state never cancels those. Only wallets",
|
|
2389
2389
|
"shown BECAUSE OF THIS STATE reserve space: they extend `gem.viewport`'s",
|
|
2390
|
-
"`reserved`, and hiding them shrinks it back to the parent's own
|
|
2390
|
+
"`reserved`, and hiding them shrinks it back to the parent's own menu button. An",
|
|
2391
2391
|
"event pop floats over the game and never moves its layout. Where the parent",
|
|
2392
2392
|
"docks its wallets beside the frame rather than over it, this changes",
|
|
2393
2393
|
"nothing.",
|
|
@@ -2416,6 +2416,108 @@
|
|
|
2416
2416
|
"rateLimit": { "perMinute": 60 }
|
|
2417
2417
|
},
|
|
2418
2418
|
|
|
2419
|
+
{
|
|
2420
|
+
"type": "gem.menu",
|
|
2421
|
+
"direction": "frameToParent",
|
|
2422
|
+
"transport": "port",
|
|
2423
|
+
"$comment": [
|
|
2424
|
+
"What the game adds to the arcade's own menu — the one control the arcade",
|
|
2425
|
+
"keeps over a game, in the frame's top-right corner, which is how a player",
|
|
2426
|
+
"leaves, mutes, reports a problem, and reaches the game's settings.",
|
|
2427
|
+
"",
|
|
2428
|
+
" settings: true the game has a settings screen and will open it when",
|
|
2429
|
+
" told to; the menu gains a Settings entry",
|
|
2430
|
+
" items up to four entries of the game's own, in this order,",
|
|
2431
|
+
" below the arcade's entries and visibly apart from them",
|
|
2432
|
+
"",
|
|
2433
|
+
"With no message at all, the menu holds the arcade's own entries and",
|
|
2434
|
+
"nothing of the game's. The arcade's entries are the arcade's: a game can",
|
|
2435
|
+
"neither remove nor reorder them, and Return to the Arcade is always there.",
|
|
2436
|
+
"",
|
|
2437
|
+
"SCREEN STATE, NOT A TOGGLE, like `gem.walletDisplay`. Each message is the",
|
|
2438
|
+
"whole answer and the latest one wins; nothing accumulates. The SDK re-sends",
|
|
2439
|
+
"the latest state after every handshake, so a parent that reconnects never",
|
|
2440
|
+
"shows a menu missing what the game had declared.",
|
|
2441
|
+
"",
|
|
2442
|
+
"`id` is the game's own name for an entry and is what comes back in",
|
|
2443
|
+
"`gem.menuSelect`; unique within one message, and the parent keeps the first",
|
|
2444
|
+
"of a repeated id. `label` is what the player reads, ALREADY IN THE PLAYER'S",
|
|
2445
|
+
"LANGUAGE (`gem.prefs.locale`): the parent does not translate it.",
|
|
2446
|
+
"",
|
|
2447
|
+
"A LABEL IS UNTRUSTED TEXT IN THE PARENT'S CHROME. A parent renders it as",
|
|
2448
|
+
"text and never as markup, in the game's group and never among its own",
|
|
2449
|
+
"entries, truncated to fit, and may decline to show an entry at all. A game",
|
|
2450
|
+
"cannot make the menu say something the arcade says: that is what the",
|
|
2451
|
+
"separate group is for.",
|
|
2452
|
+
"",
|
|
2453
|
+
"A PRESENTATION REQUEST ONLY. Not an authorization control; choosing an",
|
|
2454
|
+
"entry does nothing but send `gem.menuSelect`.",
|
|
2455
|
+
"",
|
|
2456
|
+
"Gated by `gameMenu`: sent only to a parent that advertised it. To any",
|
|
2457
|
+
"other, `gem.setMenu` returns false and the game shows a settings control",
|
|
2458
|
+
"of its own instead."
|
|
2459
|
+
],
|
|
2460
|
+
"fields": {
|
|
2461
|
+
"settings": { "kind": "boolean" },
|
|
2462
|
+
"items": {
|
|
2463
|
+
"kind": "array",
|
|
2464
|
+
"maxItems": 4,
|
|
2465
|
+
"optional": true,
|
|
2466
|
+
"items": {
|
|
2467
|
+
"kind": "object",
|
|
2468
|
+
"fields": {
|
|
2469
|
+
"id": {
|
|
2470
|
+
"kind": "string",
|
|
2471
|
+
"maxLength": 32,
|
|
2472
|
+
"$comment": "The game's own name for the entry, 1..32 characters, echoed in `gem.menuSelect`. The SDK drops an entry whose id is empty or over-long rather than cutting it into a different id."
|
|
2473
|
+
},
|
|
2474
|
+
"label": {
|
|
2475
|
+
"kind": "string",
|
|
2476
|
+
"maxLength": 40,
|
|
2477
|
+
"$comment": "What the player reads, 1..40 characters, already translated. The SDK drops an entry with an empty label and cuts an over-long one."
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
},
|
|
2483
|
+
"rateLimit": { "perMinute": 60 }
|
|
2484
|
+
},
|
|
2485
|
+
|
|
2486
|
+
{
|
|
2487
|
+
"type": "gem.menuSelect",
|
|
2488
|
+
"direction": "parentToFrame",
|
|
2489
|
+
"transport": "port",
|
|
2490
|
+
"$comment": [
|
|
2491
|
+
"The player chose one of the game's entries in the arcade's menu: its",
|
|
2492
|
+
"Settings entry, or one of the items `gem.menu` declared.",
|
|
2493
|
+
"",
|
|
2494
|
+
" entry: settings open your settings screen",
|
|
2495
|
+
" entry: item, id the item with this id",
|
|
2496
|
+
"",
|
|
2497
|
+
"Sent only for what the frame's LATEST `gem.menu` declared, and only to a",
|
|
2498
|
+
"frame that advertised `gameMenu`. The parent closes its menu first, so the",
|
|
2499
|
+
"`gem.pause` it sent when the menu opened is lifted before this arrives:",
|
|
2500
|
+
"the game is running, in front, and the next thing on screen is the game's",
|
|
2501
|
+
"own. A frame that hears an id it no longer declares ignores it — the menu",
|
|
2502
|
+
"may have been open across a change.",
|
|
2503
|
+
"",
|
|
2504
|
+
"The arcade's own entries never come through here. Leaving, the mute and",
|
|
2505
|
+
"fullscreen reach the game as they always did (`gem.pause`,",
|
|
2506
|
+
"`gem.willTerminate`, `gem.prefs`), and a problem report reaches it not at",
|
|
2507
|
+
"all."
|
|
2508
|
+
],
|
|
2509
|
+
"fields": {
|
|
2510
|
+
"entry": { "kind": "enum", "values": ["settings", "item"] },
|
|
2511
|
+
"id": {
|
|
2512
|
+
"kind": "string",
|
|
2513
|
+
"maxLength": 32,
|
|
2514
|
+
"optional": true,
|
|
2515
|
+
"$comment": "Present when `entry` is `item`: the id from `gem.menu`."
|
|
2516
|
+
}
|
|
2517
|
+
},
|
|
2518
|
+
"rateLimit": { "perMinute": 60 }
|
|
2519
|
+
},
|
|
2520
|
+
|
|
2419
2521
|
{
|
|
2420
2522
|
"type": "gem.requestShare",
|
|
2421
2523
|
"direction": "frameToParent",
|