@indiegems/gem-web-sdk 10.2.0 → 10.3.1
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 +22 -0
- package/dist/game.d.ts +49 -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 -3
- package/dist/sdk.d.ts +49 -1
- package/docs/api/api-reference-game.md +546 -36
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +683 -10
- package/docs/brand-art.md +6 -4
- package/docs/changelog.md +22 -0
- package/docs/daily-challenges.md +4 -4
- package/docs/getting-started.md +1 -1
- package/docs/inventory-and-catalogs.md +11 -7
- package/docs/manifest.json +14 -14
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +168 -90
- package/package.json +1 -1
- package/schema/bridge.v1.json +142 -14
- package/schema/capabilities.public.json +6 -0
package/docs/brand-art.md
CHANGED
|
@@ -75,7 +75,7 @@ These are the Arcade's own icon renders — the files its wallet draws — with
|
|
|
75
75
|
for byte, apart from stripping the colour-space and resolution tags every browser already assumes. They are the gem
|
|
76
76
|
of the arcade's Gems flight — a 3D model rendered in Blender, a round cut with the icon's six facet
|
|
77
77
|
colours — at rest and without its motion blur, so the gem a player sees in your price is the gem
|
|
78
|
-
that flies into
|
|
78
|
+
that flies into the Gem button when they are paid. Each size has its own recipe, in CSS px, so it carries the same visual
|
|
79
79
|
weight on every screen:
|
|
80
80
|
|
|
81
81
|
| Size | Outline | Girdle line | Facet lines | Sparkle |
|
|
@@ -282,7 +282,8 @@ too, with the ticket in place of the gem. Amended 2026-09-30 for the earn allowa
|
|
|
282
282
|
decision of that date); the ruling's other rules are unchanged. In rules:
|
|
283
283
|
|
|
284
284
|
- **Keep no Gems counter.** Your game shows no Gems balance, running total or wallet of its own, on
|
|
285
|
-
any screen. The arcade's wallet is the only place Gems accrue, and the only place a total is shown
|
|
285
|
+
any screen. The arcade's wallet is the only place Gems accrue, and the only place a total is shown
|
|
286
|
+
— in the Gem Arcade, the Gem menu, which the player opens from the Gem button at the top-left.
|
|
286
287
|
- **Draw no Gems flight or celebration of your own.** No particles flying to a corner, no counting-up
|
|
287
288
|
number, no burst of gems. `gem.cueReward` is the animation, and the arcade draws it.
|
|
288
289
|
- **Show no Gems amount at all when you are not connected to the arcade.** Without the arcade there is
|
|
@@ -306,8 +307,9 @@ decision of that date); the ruling's other rules are unchanged. In rules:
|
|
|
306
307
|
with the canvas's `getBoundingClientRect()`.
|
|
307
308
|
|
|
308
309
|
A Tickets payout is cued the same way, from the ticket, with `currencySlug: 'ARCADE_TICKETS'`. The
|
|
309
|
-
|
|
310
|
-
|
|
310
|
+
slug decides what flies — gems or tickets — and which total the platform moves. In the Gem Arcade
|
|
311
|
+
every payout flies into the Gem button at the top-left of your frame; no wallet is drawn over
|
|
312
|
+
your game.
|
|
311
313
|
- **If `cueReward` returns `false`, do nothing.** There is no fallback celebration. `false` means the
|
|
312
314
|
arcade will not show the payout here — the bridge is not up, or the embedder has no wallet — and
|
|
313
315
|
your game does not replace it with one of its own. Your reward screen still says what was earned;
|
package/docs/changelog.md
CHANGED
|
@@ -16,6 +16,28 @@ 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
|
+
## 10.3.1
|
|
20
|
+
|
|
21
|
+
Upgrading from 10.3.0. Impact: none.
|
|
22
|
+
|
|
23
|
+
Nothing in this release asks anything of a game.
|
|
24
|
+
|
|
25
|
+
## 10.3.0
|
|
26
|
+
|
|
27
|
+
Upgrading from 10.2.0. Impact: required.
|
|
28
|
+
|
|
29
|
+
### Rebuild
|
|
30
|
+
|
|
31
|
+
- The set of messages a frame sends to the Arcade has grown by one controls declaration message, so the message list and the FrameMessage union changed. Recompiling is enough. If your code switches exhaustively over frame message types, add a case for the new one.
|
|
32
|
+
- GemCapability gained a controls value. Recompiling is enough. If you map every capability to something exhaustively, add an entry for it.
|
|
33
|
+
- The recommended settings inside the preferences gained four optional fields for look sensitivity, gamepad look sensitivity, look inversion and touch movement. Recompiling is enough. The touch movement value appears as a plain string in the received preferences, while the host-side sendPrefs accepts only floating or fixed, so treat unknown values as the default.
|
|
34
|
+
|
|
35
|
+
### New capability
|
|
36
|
+
|
|
37
|
+
- Your game can now declare which control choices it offers by using Gem.controls, which is a ControlsApi, or GemFrame.declareControls. You describe the choices with GemControlChoice entries inside a GemControlsDeclaration. Declaring them lets the Arcade show the matching camera and controls settings for your game. Check that the new controls capability is available before declaring.
|
|
38
|
+
- The SDK now provides camera look input. GemLook delivers GemLookDelta values, configured with GemLookOptions and scaled by a GemLookSpeed. GemLookBase gives the base look speed. Use these instead of your own look scaling so that the player's sensitivity choices apply to your game.
|
|
39
|
+
- The player's recommended settings now carry four optional fields: lookSensitivity, gamepadLookSensitivity, invertLookY and touchMovement. Read them to start with the player's preferred look sensitivity, look inversion and touch movement style. They may be absent, so keep your own defaults for when they are not provided.
|
|
40
|
+
|
|
19
41
|
## 10.2.0
|
|
20
42
|
|
|
21
43
|
Upgrading from 10.1.1. Impact: required.
|
package/docs/daily-challenges.md
CHANGED
|
@@ -320,11 +320,11 @@ Four things to know before you ship one:
|
|
|
320
320
|
in" is not in general when the allowance refills. Each date's allowance is spent once, whatever
|
|
321
321
|
the player does to their time zone.
|
|
322
322
|
|
|
323
|
-
**When a completion pays a platform currency, show it where it happened.** The arcade
|
|
324
|
-
player's Gems and Tickets in its own chrome, and without a cue a payout
|
|
325
|
-
|
|
323
|
+
**When a completion pays a platform currency, show it where it happened.** The arcade shows the
|
|
324
|
+
player's Gems and Tickets in its own chrome, and without a cue a payout flies from the middle of
|
|
325
|
+
your frame, not from the payout the player is looking at. At the moment your game shows the challenge complete, call
|
|
326
326
|
`gem.cueReward` with the currency that paid (`GEMS` or `ARCADE_TICKETS`) and the centre of its icon
|
|
327
|
-
beside the payout on your completion screen, and the payout flies from there into the
|
|
327
|
+
beside the payout on your completion screen, and the payout flies from there into the Gem button:
|
|
328
328
|
[inventory-and-catalogs.md](inventory-and-catalogs.md#showing-the-reward-land-in-the-arcades-wallet).
|
|
329
329
|
**If it returns `false`, do nothing** — no celebration of your own, no counter, no flight;
|
|
330
330
|
platform-currency display belongs to the arcade, and a game not connected to it shows no
|
package/docs/getting-started.md
CHANGED
|
@@ -589,14 +589,15 @@ the source progression from a function the player calls, on a progression that o
|
|
|
589
589
|
### Showing the reward land in the arcade's wallet
|
|
590
590
|
|
|
591
591
|
**Platform-currency display belongs to the arcade** (the creator's ruling, 2026-09-24) — Gems and
|
|
592
|
-
Tickets alike. The arcade
|
|
593
|
-
|
|
592
|
+
Tickets alike. The arcade shows the player's platform currencies in its own chrome — in the Gem
|
|
593
|
+
Arcade, the Gem menu the player opens from the Gem button at the top-left of your frame; no wallet
|
|
594
|
+
is drawn over your game — and moves a total when the platform confirms a grant. Your game keeps no counter or balance
|
|
594
595
|
of a platform currency of its own, draws no flight or celebration of its own, and shows no
|
|
595
596
|
platform-currency amount at all when it is not connected to the arcade.
|
|
596
597
|
|
|
597
598
|
What your game does is ask for the payout to start on **your** screen, from the currency's icon (or
|
|
598
|
-
hero) beside the reward, so the currency flies from there into the
|
|
599
|
-
as
|
|
599
|
+
hero) beside the reward, so the currency flies from there into the arcade — in the Gem Arcade,
|
|
600
|
+
into the Gem button, which pops as each mark lands. A Gems payout is cued from the gem:
|
|
600
601
|
|
|
601
602
|
```js
|
|
602
603
|
const { x, y, width, height } = rewardGem.getBoundingClientRect(); // the gem element's own box
|
|
@@ -609,8 +610,10 @@ gem.cueReward({
|
|
|
609
610
|
|
|
610
611
|
`origin` is the **centre of the gem on screen** — measure the gem element itself, not the row or
|
|
611
612
|
panel around it. A Tickets payout is the same call with `currencySlug: 'ARCADE_TICKETS'`, from the
|
|
612
|
-
ticket: the slug decides which total
|
|
613
|
-
|
|
613
|
+
ticket: the slug decides what flies and which total moves. **A payout you do not cue still flies**
|
|
614
|
+
in the Gem Arcade, from the middle of your frame, once the platform confirms it — cue it so the
|
|
615
|
+
flight starts where the player is looking. **If `cueReward` returns `false`, do nothing**: there is
|
|
616
|
+
no fallback celebration.
|
|
614
617
|
`false` means the arcade will not show it here — the bridge is not up, or the embedder has no wallet
|
|
615
618
|
— and your game does not draw one of its own in its place.
|
|
616
619
|
|
|
@@ -898,7 +901,8 @@ buyButton.disabled = !gems || gems.balance < price; // can the player afford it?
|
|
|
898
901
|
```
|
|
899
902
|
|
|
900
903
|
**Read it to decide, never to display.** Platform-currency display belongs to the arcade: its
|
|
901
|
-
wallet shows the player's Gems and Tickets totals while your
|
|
904
|
+
wallet — the Gem menu, in the Gem Arcade — shows the player's Gems and Tickets totals while your
|
|
905
|
+
game runs, and your game keeps no
|
|
902
906
|
counter, balance or wallet total of either of its own ([brand-art.md](brand-art.md) §5). Use the
|
|
903
907
|
balance to enable a purchase or to say "not enough Gems" — not to draw a number.
|
|
904
908
|
|
package/docs/manifest.json
CHANGED
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"file": "brand-art.md",
|
|
31
|
-
"sha256": "
|
|
32
|
-
"updated": "2026-10-
|
|
31
|
+
"sha256": "87267dca53e5506b1aee77ac9819aef8025c8e0de2780e65a2ea3b7236a9c0ab",
|
|
32
|
+
"updated": "2026-10-11T18:48:12Z"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"file": "changelog.md",
|
|
36
|
-
"sha256": "
|
|
37
|
-
"updated": "2026-10-
|
|
36
|
+
"sha256": "25047a1c8498f92990b4d50768cf49d1cd4acd2a27dd42db595ca322aa56a2a9",
|
|
37
|
+
"updated": "2026-10-11T19:08:27Z"
|
|
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": "0423168355c22724e705def15ca19a5651b7f0da3598ec5aabcc69c32e0bb401",
|
|
47
|
+
"updated": "2026-10-11T18:48:12Z"
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"file": "dedicated-server-flow.md",
|
|
@@ -68,13 +68,13 @@
|
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"file": "getting-started.md",
|
|
71
|
-
"sha256": "
|
|
72
|
-
"updated": "2026-10-
|
|
71
|
+
"sha256": "327be77c7b742f7ecfb3ca8f4869480e72942ea9f7293e25634c47c056a38eae",
|
|
72
|
+
"updated": "2026-10-11T19:08:27Z"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"file": "inventory-and-catalogs.md",
|
|
76
|
-
"sha256": "
|
|
77
|
-
"updated": "2026-10-
|
|
76
|
+
"sha256": "fecd1299ef19bc50ea17abe96bbe49f62dc366a9fc0f354c843a42b2d5a2f9e2",
|
|
77
|
+
"updated": "2026-10-11T18:48:12Z"
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"file": "leaderboards.md",
|
|
@@ -123,8 +123,8 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"file": "server-bundles.md",
|
|
126
|
-
"sha256": "
|
|
127
|
-
"updated": "2026-10-
|
|
126
|
+
"sha256": "c6bf3689c928b6cb24d86faa380e6ed4bece1f3dd6e8dfc71c1ff9de1ed0dd42",
|
|
127
|
+
"updated": "2026-10-11T19:08:27Z"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
130
|
"file": "serverless-functions-js.md",
|
|
@@ -148,8 +148,8 @@
|
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"file": "what-a-game-must-do.md",
|
|
151
|
-
"sha256": "
|
|
152
|
-
"updated": "2026-10-
|
|
151
|
+
"sha256": "c5797bd913fbd8c62fb3fbe83b6c8c6cb22f5aec984c0d56f3f0198c917fee7d",
|
|
152
|
+
"updated": "2026-10-11T18:48:12Z"
|
|
153
153
|
}
|
|
154
154
|
]
|
|
155
155
|
}
|
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/v10.
|
|
84
|
-
curl -fsSL https://downloads.gemarcade.com/sdk/v10.
|
|
83
|
+
curl -fsSL -O https://downloads.gemarcade.com/sdk/v10.3.1/gem-web-sdk.tgz
|
|
84
|
+
curl -fsSL https://downloads.gemarcade.com/sdk/v10.3.1/gem-web-sdk.tgz.sri
|
|
85
85
|
openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -702,53 +702,49 @@ the `var()` above, or read `gem.viewport`. The release checklist looks for the b
|
|
|
702
702
|
|
|
703
703
|
### The arcade's own controls: reserved areas
|
|
704
704
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
705
|
+
The Arcade keeps **one control of its own over every game**, on every device — a phone's full-bleed
|
|
706
|
+
game, a framed or full-window game on a desktop, a tablet or a TV in native fullscreen:
|
|
707
|
+
|
|
708
|
+
- **The Gem button**, the Gem Arcade's gem, in the **top-left corner**, always there (a touch target, about
|
|
709
|
+
50px across). It opens [the Arcade's menu](#the-arcades-menu): Return to the Arcade, the player's Gems,
|
|
710
|
+
Tickets and achievement score, mute, the player's Arcade-wide settings and the rest. Not your
|
|
711
|
+
settings: those are [your own button](#game-settings).
|
|
712
|
+
- **Nothing else.** The player's wallets (Gems, Tickets, their achievement score) are **not** drawn
|
|
713
|
+
over your game: the player opens the Gem menu to see them, and every platform-currency payout
|
|
714
|
+
flies into the Gem button ([the Arcade's wallet](#the-arcades-wallet-in-the-gem-menu-never-drawn-by-you)).
|
|
715
|
+
When the player switches the frame-rate readout on, it sits beside the gem and is reserved the
|
|
716
|
+
same way.
|
|
714
717
|
|
|
715
718
|
**The top of the screen has one layout, in every game:**
|
|
716
719
|
|
|
717
|
-
| Where
|
|
718
|
-
|
|
|
719
|
-
| **Top-left**
|
|
720
|
-
| **
|
|
721
|
-
| **Centre
|
|
722
|
-
|
|
723
|
-
Keep the left for Home / Back even on a screen that has neither today: a player learns where "back"
|
|
724
|
-
is once, across every game, and information that sits there on one screen teaches them the wrong
|
|
725
|
-
thing on the next.
|
|
720
|
+
| Where | Whose | What goes there |
|
|
721
|
+
| ------------------------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
722
|
+
| **Top-left corner** | the Arcade's | Its Gem button. Nothing of yours under it. |
|
|
723
|
+
| **Top-left, right of the Gem** | yours | Your **Home / Back** buttons, drawn when a screen needs them, level with the Gem and just to its right. They move around _inside_ your game — back to your level select or title screen — and never leave it. |
|
|
724
|
+
| **Centre of the top row** | yours | Your **information**: score, moves, a timer, a level name. |
|
|
725
|
+
| **Top-right** | yours | Your **settings** button ([Game settings](#game-settings)). |
|
|
726
726
|
|
|
727
|
-
|
|
728
|
-
|
|
727
|
+
Keep the spot beside the Gem for Home / Back even on a screen that has neither today: a player
|
|
728
|
+
learns where "back" is once, across every game, and information that sits there on one screen
|
|
729
|
+
teaches them the wrong thing on the next.
|
|
729
730
|
|
|
730
|
-
**
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
the Arcade's. Do not copy those numbers — read the space, as below.
|
|
731
|
+
**Your settings button goes top-right**, clear of any Arcade control: under the Gem Arcade there is
|
|
732
|
+
none in that corner, but another embedder may keep one there. [Game settings](#game-settings) shows
|
|
733
|
+
how to place it.
|
|
734
734
|
|
|
735
735
|
Those rectangles of your frame are **reserved**: keep essential UI and every touch target out of
|
|
736
|
-
them. **Never hard-code their size or
|
|
737
|
-
|
|
736
|
+
them. **Never hard-code their size or position.** They differ by phone, and in native fullscreen the
|
|
737
|
+
gem may rise into the camera band — read them, as below.
|
|
738
738
|
|
|
739
|
-
An inset cannot say that — as a top inset, a button a few dozen pixels wide would take
|
|
740
|
-
strip. So `gem.viewport` also carries the rectangles, in the same CSS pixels of your
|
|
741
|
-
from its top-left corner — one
|
|
739
|
+
An inset cannot say that — as a left or top inset, a button a few dozen pixels wide would take a whole
|
|
740
|
+
strip of your screen. So `gem.viewport` also carries the rectangles, in the same CSS pixels of your
|
|
741
|
+
frame, measured from its top-left corner — one for the Gem button, and one for the frame-rate
|
|
742
|
+
readout while the player shows it:
|
|
742
743
|
|
|
743
744
|
```json
|
|
744
745
|
{
|
|
745
746
|
"insets": { "top": 47, "right": 0, "bottom": 34, "left": 0 },
|
|
746
|
-
"reserved": [
|
|
747
|
-
{ "x": 68, "y": 51, "width": 72, "height": 28 },
|
|
748
|
-
{ "x": 144, "y": 51, "width": 72, "height": 28 },
|
|
749
|
-
{ "x": 220, "y": 51, "width": 72, "height": 28 },
|
|
750
|
-
{ "x": 304, "y": 51, "width": 48, "height": 48 }
|
|
751
|
-
]
|
|
747
|
+
"reserved": [{ "x": 8, "y": 55, "width": 52, "height": 52 }]
|
|
752
748
|
}
|
|
753
749
|
```
|
|
754
750
|
|
|
@@ -756,59 +752,44 @@ from its top-left corner — one per wallet shown, and one for the menu button:
|
|
|
756
752
|
`--gem-safe-*` before anything else hears the push: `--gem-reserved-tl-width` and
|
|
757
753
|
`--gem-reserved-tl-height`, and the same pair for `tr`, `bl` and `br` — how far that corner's
|
|
758
754
|
reserved rectangles reach in from its two edges. A rectangle belongs to the corner its centre lies
|
|
759
|
-
nearest to.
|
|
760
|
-
|
|
761
|
-
⚠️ **The wallets sit in the middle, so they count toward BOTH top corners.** A wallet left of
|
|
762
|
-
centre is a top-left rectangle, one right of centre a top-right one. While wallets are shown,
|
|
763
|
-
`--gem-reserved-tl-height` is the bottom of the wallets and `--gem-reserved-tl-width` reaches to the
|
|
764
|
-
middle of the screen — even though the corner itself, left of the first wallet, is clear. So:
|
|
755
|
+
nearest to. Under the Gem Arcade the Gem button is a top-left rectangle, so:
|
|
765
756
|
|
|
766
757
|
```css
|
|
767
|
-
/*
|
|
768
|
-
the two top corners. It moves up by itself when gameplay hides the wallets. */
|
|
769
|
-
#info {
|
|
770
|
-
top: max(
|
|
771
|
-
var(--gem-safe-top, env(safe-area-inset-top, 0px)),
|
|
772
|
-
var(--gem-reserved-tl-height, 0px),
|
|
773
|
-
var(--gem-reserved-tr-height, 0px)
|
|
774
|
-
);
|
|
775
|
-
right: var(--gem-safe-right, 0px);
|
|
776
|
-
}
|
|
777
|
-
/* ONE Home or Back button, level with the arcade's row, in the clear corner to the wallets' left */
|
|
758
|
+
/* ONE Home or Back button, level with the Gem, just to its right */
|
|
778
759
|
#back {
|
|
779
760
|
top: var(--gem-safe-top, env(safe-area-inset-top, 0px));
|
|
780
|
-
left: var(--gem-safe-left, 0px);
|
|
761
|
+
left: max(var(--gem-safe-left, 0px), var(--gem-reserved-tl-width, 0px));
|
|
781
762
|
}
|
|
782
|
-
/*
|
|
783
|
-
|
|
784
|
-
#nav {
|
|
763
|
+
/* your information — score, moves, a timer — clear of every top corner the arcade reserves */
|
|
764
|
+
#info {
|
|
785
765
|
top: max(
|
|
786
766
|
var(--gem-safe-top, env(safe-area-inset-top, 0px)),
|
|
787
|
-
var(--gem-reserved-tl-height, 0px),
|
|
788
767
|
var(--gem-reserved-tr-height, 0px)
|
|
789
768
|
);
|
|
769
|
+
left: max(var(--gem-safe-left, 0px), var(--gem-reserved-tl-width, 0px));
|
|
770
|
+
right: var(--gem-safe-right, 0px);
|
|
790
771
|
}
|
|
791
772
|
```
|
|
792
773
|
|
|
793
|
-
The per-corner properties
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
774
|
+
The per-corner properties say how far a corner's rectangles reach, not how much room is left beside
|
|
775
|
+
them. To know whether a row of your own buttons fits beside the Gem, read the rectangles from
|
|
776
|
+
script: the room to the right of the top-left ones is the frame's width less the furthest right
|
|
777
|
+
edge among them.
|
|
797
778
|
|
|
798
779
|
```js
|
|
799
780
|
const reserved = gem.viewport?.reserved ?? [];
|
|
800
|
-
const
|
|
801
|
-
|
|
781
|
+
const topLeft = reserved.filter((r) => r.x + r.width / 2 < window.innerWidth / 2);
|
|
782
|
+
const from = topLeft.length ? Math.max(...topLeft.map((r) => r.x + r.width)) : 0;
|
|
783
|
+
nav.classList.toggle('below-arcade-row', window.innerWidth - from < nav.offsetWidth + 8);
|
|
802
784
|
```
|
|
803
785
|
|
|
804
786
|
All eight properties are written on every push, `0px` for a clear corner, so a rotation never leaves
|
|
805
787
|
a stale value; they are never set under an embedder that sends no viewport, which is what the `0px`
|
|
806
788
|
fallback is for. `gem.viewport.reserved` is `[]` when nothing is covered.
|
|
807
789
|
|
|
808
|
-
**Lay out against it whenever it changes.** The
|
|
809
|
-
session
|
|
810
|
-
|
|
811
|
-
all four is right under any embedder.
|
|
790
|
+
**Lay out against it whenever it changes.** The Gem button's rectangle is there for the whole
|
|
791
|
+
session, and it moves with a rotation, a resize or the frame-rate readout. Read every corner, not
|
|
792
|
+
only the one this page names today: a layout that honours all four is right under any embedder.
|
|
812
793
|
|
|
813
794
|
**Older SDKs get it folded into the top inset.** A game on an SDK from before reserved areas cannot
|
|
814
795
|
read the rectangles, so the arcade sends it none and instead raises `insets.top` — and so
|
|
@@ -939,6 +920,16 @@ same push as `muted` and `reducedMotion`, and again whenever the player changes
|
|
|
939
920
|
| `keyboardLayout` | `'wasd'` / `'arrows'` | Which keys move by default. |
|
|
940
921
|
| `gamepadConfirm` | `'south'` / `'east'` | Which face button confirms, by position: the bottom one (A on an Xbox controller) or the right one (B). The other one goes back. |
|
|
941
922
|
| `vibration` | `true` / `false` | Your vibration toggle's starting state ([if your game vibrates](#if-your-game-vibrates)). |
|
|
923
|
+
| `lookSensitivity` | `20` to `400` | Look speed for touch, mouse and pen, as a percentage of the base speeds: `100` is 1×. |
|
|
924
|
+
| `gamepadLookSensitivity` | `20` to `400` | Look speed for a gamepad stick, as a percentage: `100` is 1×. |
|
|
925
|
+
| `invertLookY` | `true` / `false` | Up/down look inverted: pushing up or dragging up looks down. Pitch only, never yaw. |
|
|
926
|
+
| `touchMovement` | `'floating'` / `'fixed'` | On a touch screen, a movement stick that appears where the thumb lands, or one fixed in place. |
|
|
927
|
+
|
|
928
|
+
The last four are the camera and controls settings: see
|
|
929
|
+
[camera and look controls](#camera-and-look-controls) for telling the Arcade which of them your game
|
|
930
|
+
uses, and for `gem.controls.look()`, which applies the two sensitivities and the invert for you. An
|
|
931
|
+
older Arcade sends `recommended` without them; the SDK then fills each with its default (`100`,
|
|
932
|
+
`100`, `false`, `'floating'`), so whenever `recommended` is not `null` all nine fields are there.
|
|
942
933
|
|
|
943
934
|
**They are recommendations, and the player's own choice in your game always wins:**
|
|
944
935
|
|
|
@@ -955,7 +946,10 @@ same push as `muted` and `reducedMotion`, and again whenever the player changes
|
|
|
955
946
|
`keyboardLayout`.
|
|
956
947
|
|
|
957
948
|
```js
|
|
958
|
-
const DEFAULTS = {
|
|
949
|
+
const DEFAULTS = {
|
|
950
|
+
volume: 100, batterySaver: false, keyboardLayout: 'wasd', gamepadConfirm: 'south', vibration: true,
|
|
951
|
+
lookSensitivity: 100, gamepadLookSensitivity: 100, invertLookY: false, touchMovement: 'floating',
|
|
952
|
+
};
|
|
959
953
|
let chosen = {}; // ONLY what the player picked in your settings screen
|
|
960
954
|
|
|
961
955
|
// The player's choice, then the Arcade's recommendation, then your own default.
|
|
@@ -985,11 +979,99 @@ player's choices are kept between sessions.
|
|
|
985
979
|
([sound](#sound-your-own-mute-control-and-the-arcades)), and `reducedMotion` is the player's
|
|
986
980
|
accessibility setting, which your game honours.
|
|
987
981
|
|
|
982
|
+
## Camera and look controls
|
|
983
|
+
|
|
984
|
+
If your game turns a camera — a drag on a phone, a mouse under pointer lock, a gamepad's right
|
|
985
|
+
stick — the player has four settings for it in the Arcade, per device, the way Roblox players do:
|
|
986
|
+
**look sensitivity**, **gamepad look sensitivity**, **invert look up/down** and **touch movement**
|
|
987
|
+
(a floating or a fixed thumbstick). They reach you in `gem.prefs.recommended`
|
|
988
|
+
([the table above](#start-from-the-players-arcade-wide-defaults-gemprefsrecommended)). Two calls
|
|
989
|
+
make them work.
|
|
990
|
+
|
|
991
|
+
**1. Declare which ones your game uses — `gem.controls.declare`.** For each setting, `'player'`
|
|
992
|
+
means you apply the Arcade's value (or the player's own choice in your settings, which still wins);
|
|
993
|
+
`'game'` means your game decides it, and the Arcade's Settings shows that row as "Set by this game";
|
|
994
|
+
leave out a setting your game does not use, and the Arcade says so beside it. Call it once at boot:
|
|
995
|
+
|
|
996
|
+
```js
|
|
997
|
+
gem.controls.declare({
|
|
998
|
+
lookSensitivity: 'player',
|
|
999
|
+
gamepadLookSensitivity: 'player',
|
|
1000
|
+
invertLookY: 'player',
|
|
1001
|
+
touchMovement: 'game', // this game always draws a fixed stick
|
|
1002
|
+
});
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
It is screen state, like [`gem.setWalletVisible`](#the-arcades-wallet-in-the-gem-menu-never-drawn-by-you):
|
|
1006
|
+
each call is the whole answer, the latest wins, and the SDK holds it and sends it again after a
|
|
1007
|
+
reconnect. It returns `false` before the bridge is up (it is held and sent on connect) and under an
|
|
1008
|
+
embedder that does not show these settings; it never throws. A key or a value other than these is
|
|
1009
|
+
dropped.
|
|
1010
|
+
|
|
1011
|
+
**2. Turn look input into a camera turn — `gem.controls.look()`.** It is pure math: it listens to
|
|
1012
|
+
nothing and moves no camera. Give it the deltas your input code already has; it returns radians to
|
|
1013
|
+
**add**, `yaw` positive turning right and `pitch` positive looking up, at Roblox's base speeds
|
|
1014
|
+
(`gem.controls.LOOK_BASE`: touch 1° across and 0.66° up/down per CSS px, mouse 0.5° and 0.385°,
|
|
1015
|
+
a stick 240° and 184.8° a second at full tilt), times the player's sensitivity, with the invert
|
|
1016
|
+
applied. It also does what is easy to get wrong: the stick's deadzone (0.1) and curve, dragging up
|
|
1017
|
+
to look up, and on touch a slower pitch as the camera nears straight up or down.
|
|
1018
|
+
|
|
1019
|
+
```js
|
|
1020
|
+
import * as THREE from 'three';
|
|
1021
|
+
|
|
1022
|
+
const camera = new THREE.PerspectiveCamera(70, innerWidth / innerHeight, 0.1, 500);
|
|
1023
|
+
camera.rotation.order = 'YXZ'; // yaw, then pitch
|
|
1024
|
+
let yaw = 0;
|
|
1025
|
+
let pitch = 0;
|
|
1026
|
+
const look = gem.controls.look(); // make one, keep it
|
|
1027
|
+
|
|
1028
|
+
function turn({ yaw: dYaw, pitch: dPitch }) {
|
|
1029
|
+
yaw -= dYaw; // three.js: a positive rotation.y turns LEFT, so subtract to turn right
|
|
1030
|
+
pitch = THREE.MathUtils.clamp(pitch + dPitch, -1.45, 1.45); // your clamp, as always
|
|
1031
|
+
camera.rotation.set(pitch, yaw, 0);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// Touch and pen: the change since the last event. A mouse: movementX/Y under pointer lock.
|
|
1035
|
+
let last = null;
|
|
1036
|
+
canvas.addEventListener('pointerdown', (e) => { last = { x: e.clientX, y: e.clientY }; });
|
|
1037
|
+
canvas.addEventListener('pointerup', () => { last = null; });
|
|
1038
|
+
canvas.addEventListener('pointermove', (e) => {
|
|
1039
|
+
if (e.pointerType === 'mouse') {
|
|
1040
|
+
if (document.pointerLockElement === canvas) turn(look.fromPointer(e.movementX, e.movementY, 'mouse'));
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
if (!last) return;
|
|
1044
|
+
turn(look.fromPointer(e.clientX - last.x, e.clientY - last.y, e.pointerType, pitch));
|
|
1045
|
+
last = { x: e.clientX, y: e.clientY };
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
// The right stick, once per frame.
|
|
1049
|
+
function frame(dt) {
|
|
1050
|
+
const pad = navigator.getGamepads().find(Boolean);
|
|
1051
|
+
if (pad) turn(look.fromStick(pad.axes[2], pad.axes[3], dt));
|
|
1052
|
+
}
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
- **Read live.** The sensitivities and the invert are read from `gem.prefs.recommended` on every
|
|
1056
|
+
call, so a change the player makes in the Arcade's Settings mid-game applies on the next move.
|
|
1057
|
+
Do not copy them into variables at boot.
|
|
1058
|
+
- **The player's choice in your game wins.** If your settings screen has its own look speed or
|
|
1059
|
+
invert, pass it — `gem.controls.look({ sensitivity: 1.5, invertY: true })`, or later
|
|
1060
|
+
`look.set({ sensitivity: 1.5 })` — and that input stops following the Arcade.
|
|
1061
|
+
`look.set({ sensitivity: undefined })` hands it back. `sensitivity` and `gamepadSensitivity` are
|
|
1062
|
+
multipliers (`1` is the base speed); the Arcade's percentages are divided by 100 for you.
|
|
1063
|
+
- **Pass `pitch` for touch**, the camera's current pitch in radians (0 at the horizon, positive
|
|
1064
|
+
looking up), or the slowdown near vertical is skipped. A mouse and a stick are never slowed.
|
|
1065
|
+
- **`touchMovement` is yours to draw.** The SDK has no thumbstick: read
|
|
1066
|
+
`gem.prefs.recommended?.touchMovement` and draw a floating or a fixed one, or declare
|
|
1067
|
+
`touchMovement: 'game'` if your game only has one kind.
|
|
1068
|
+
- **No camera, no call.** A game with no camera to turn declares nothing and never calls `look`.
|
|
1069
|
+
|
|
988
1070
|
## The Arcade's menu
|
|
989
1071
|
|
|
990
|
-
The Arcade keeps one
|
|
991
|
-
(**Return to the Arcade**),
|
|
992
|
-
settings — the ones that reach you as [`gem.prefs.recommended`](#game-settings). You can add up to
|
|
1072
|
+
The Arcade keeps one button over every game, on every device: the Gem, at the top-left. Its menu is
|
|
1073
|
+
how a player leaves (**Return to the Arcade**), sees their Gems, Tickets and achievement score, mutes,
|
|
1074
|
+
goes fullscreen, reports a problem and changes their Arcade-wide settings — the ones that reach you as [`gem.prefs.recommended`](#game-settings). You can add up to
|
|
993
1075
|
four entries of your own:
|
|
994
1076
|
|
|
995
1077
|
```js
|
|
@@ -1106,28 +1188,24 @@ its music out than cut it, say. The Arcade still shows its button for your game
|
|
|
1106
1188
|
so this is a promise: your `onPrefs` handler must go quiet on `muted: true` and come back on `false`,
|
|
1107
1189
|
and the release checklist checks that it does.
|
|
1108
1190
|
|
|
1109
|
-
## The Arcade's wallet:
|
|
1191
|
+
## The Arcade's wallet: in the Gem menu, never drawn by you
|
|
1110
1192
|
|
|
1111
1193
|
The player's platform balances — Gems, Tickets (`ARCADE_TICKETS`) and their achievement score —
|
|
1112
1194
|
belong to the Arcade. **Your game never draws one**, and never shows a pending state for one: the
|
|
1113
|
-
Arcade takes every figure from the platform and
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
[
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
Read the `--gem-reserved-*` properties rather than assuming either state.
|
|
1128
|
-
|
|
1129
|
-
⚠️ **The Arcade ignores the `only` option**: a shown wallet is every wallet. Do not build a store
|
|
1130
|
-
screen that relies on narrowing them to one currency.
|
|
1195
|
+
Arcade takes every figure from the platform and shows them itself.
|
|
1196
|
+
|
|
1197
|
+
**The Gem Arcade draws no wallet over your game** (since 2026-10-11). Nothing of it sits in your top
|
|
1198
|
+
row, at rest or when a balance moves. The player opens the Gem menu — the Gem button at the
|
|
1199
|
+
top-left — to see their Gems, Tickets and achievement score, and every payout
|
|
1200
|
+
[`gem.cueReward`](inventory-and-catalogs.md#showing-the-reward-land-in-the-arcades-wallet) announces
|
|
1201
|
+
flies into the Gem button. A payout you do not cue flies too, from the middle of your frame, so cue
|
|
1202
|
+
the ones the player earns on screen and the flight starts where they are looking.
|
|
1203
|
+
|
|
1204
|
+
**`gem.setWalletVisible` changes nothing on the Gem Arcade.** It is still part of the contract, so a
|
|
1205
|
+
game that calls it keeps working, and it is the call to make for an embedder that draws wallets over
|
|
1206
|
+
games: hide them while you play, show them on your menus. Under the Gem Arcade there is no wallet to
|
|
1207
|
+
show or hide and no wallet rectangle in `gem.viewport.reserved`. The `only` option is ignored there
|
|
1208
|
+
as well.
|
|
1131
1209
|
|
|
1132
1210
|
## No accidental text selection
|
|
1133
1211
|
|