@indiegems/gem-web-sdk 8.0.0 → 8.1.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 +15 -0
- package/dist/game.d.ts +13 -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 +12 -3
- package/dist/sdk.d.ts +13 -1
- package/docs/achievements.md +72 -5
- package/docs/api/api-reference-game.md +296 -17
- package/docs/api/api-reference-server.md +1 -1
- package/docs/api/reference.json +231 -11
- package/docs/changelog.md +15 -0
- package/docs/cross-promotion.md +38 -2
- package/docs/getting-started.md +3 -2
- package/docs/manifest.json +12 -12
- package/docs/server-bundles.md +2 -2
- package/docs/what-a-game-must-do.md +124 -3
- package/package.json +1 -1
- package/schema/bridge.v1.json +42 -1
- package/schema/capabilities.public.json +6 -0
|
@@ -353,6 +353,117 @@ 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
|
+
**The same declaration decides how a computer or a television shows your game.** Neither is held
|
|
357
|
+
two ways round, so the Arcade reads your phone and tablet rows instead and, for a portrait-only
|
|
358
|
+
game, draws a centred portrait column.
|
|
359
|
+
[Art beside a portrait game](#art-beside-a-portrait-game-on-a-wide-screen-portrait-borders) below
|
|
360
|
+
has the exact rule, and the art you can put beside that column.
|
|
361
|
+
|
|
362
|
+
## Art beside a portrait game on a wide screen: portrait borders
|
|
363
|
+
|
|
364
|
+
**You need this only if your game is portrait-only and you want your own art beside it on a
|
|
365
|
+
computer or a television.** On a desktop browser, the Arcade's desktop app or a TV, the Arcade
|
|
366
|
+
draws a portrait-only game as a centred 9:16 column and fills the space either side with its own
|
|
367
|
+
bars. Portrait borders put your art there instead: you publish a named set of images with your
|
|
368
|
+
store page, and your game says which one goes with what is on screen.
|
|
369
|
+
|
|
370
|
+
### When the Arcade letterboxes a game
|
|
371
|
+
|
|
372
|
+
**Your store page's `orientations` decide it, and nothing else does.** Declaring borders never
|
|
373
|
+
turns the letterbox on, and there is no desktop orientation to declare. The rule:
|
|
374
|
+
|
|
375
|
+
1. Take `tablet` if you declared both its `portrait` and its `landscape`; otherwise `phone` if you
|
|
376
|
+
declared both of its; otherwise stop, and the game is not letterboxed.
|
|
377
|
+
2. The game is letterboxed to portrait when, on that device, `portrait` is `full` or `partial` and
|
|
378
|
+
`landscape` is `none`.
|
|
379
|
+
|
|
380
|
+
| `orientations`, each pair portrait / landscape | Letterboxed on a computer or TV |
|
|
381
|
+
| ---------------------------------------------- | ------------------------------- |
|
|
382
|
+
| nothing declared | no |
|
|
383
|
+
| tablet `full` / `none` | yes |
|
|
384
|
+
| phone `full` / `none`, no tablet | yes |
|
|
385
|
+
| phone `partial` / `none`, no tablet | yes |
|
|
386
|
+
| phone `full` / `none`, tablet `full` / `full` | no — the tablet decides |
|
|
387
|
+
| phone `full` / `none`, tablet `none` / `full` | no — the tablet decides |
|
|
388
|
+
| tablet `none` / `full` | no |
|
|
389
|
+
| tablet `partial` / `partial` | no |
|
|
390
|
+
| tablet `full` / `partial` | no |
|
|
391
|
+
|
|
392
|
+
A phone or a tablet is never letterboxed: it gets the rotate panel from the section above. On a
|
|
393
|
+
computer or TV, a game the rule does not letterbox fills the play area and its borders never show.
|
|
394
|
+
**A store page with borders and orientations that never letterbox uploads with a
|
|
395
|
+
`portrait_borders_unused` warning** — the page is accepted, and the art will not appear anywhere.
|
|
396
|
+
|
|
397
|
+
### Declaring the borders
|
|
398
|
+
|
|
399
|
+
Add them to your store page's `media`, one entry per image:
|
|
400
|
+
|
|
401
|
+
```yaml
|
|
402
|
+
media:
|
|
403
|
+
portrait_borders:
|
|
404
|
+
- name: meadow # the first entry is the default
|
|
405
|
+
src: borders/meadow.jpg
|
|
406
|
+
- name: alpine
|
|
407
|
+
src: borders/alpine.jpg
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
- **Up to 16 entries**, each with a `name` and a `src`. A name is 1 to 40 characters of `a-z`,
|
|
411
|
+
`0-9`, `_` and `-`, starts with a letter or digit, and is unique on the page.
|
|
412
|
+
- **The first entry is the default.** The Arcade shows it the moment play starts, before your game
|
|
413
|
+
has said anything, so put first the one most players should see.
|
|
414
|
+
- **16:9, at least 1920×1080; 2560×1440 is ideal.** PNG, JPEG or WebP. An AVIF, GIF or SVG uploads
|
|
415
|
+
without complaint and then fails the page's processing.
|
|
416
|
+
- **Opaque.** A transparent area shows the Arcade's own bars through it.
|
|
417
|
+
- **Keep the interesting part away from the middle.** The image is scaled to cover the whole play
|
|
418
|
+
area and centred, and your game covers the centre. How much shows either side changes with the
|
|
419
|
+
shape of the player's screen — in a window narrower than the column, the art shows above and
|
|
420
|
+
below instead — so nothing in the art can line up with your game's edge.
|
|
421
|
+
- **Upload with a current `gemctl`.** An older one refuses `portrait_borders` as an unknown key.
|
|
422
|
+
|
|
423
|
+
### Choosing one from your game
|
|
424
|
+
|
|
425
|
+
Call `gem.showPortraitBorders` at start-up and on every theme change, with the name of the border
|
|
426
|
+
that goes with what is on screen, and `null` for a theme that has no art:
|
|
427
|
+
|
|
428
|
+
```js
|
|
429
|
+
const BORDERS = new Set(['meadow', 'alpine']); // the names your store page declares
|
|
430
|
+
|
|
431
|
+
function applyTheme(id) {
|
|
432
|
+
document.body.dataset.theme = id;
|
|
433
|
+
gem.showPortraitBorders(BORDERS.has(id) ? id : null); // null: the Arcade's own bars show again
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
- **Call it unconditionally.** On a phone, or on a screen where your game is not letterboxed, the
|
|
438
|
+
Arcade keeps your choice and shows nothing, and nothing tells your game which case it is in — do
|
|
439
|
+
not look for a signal. `gem.frame.parentSupports('portraitBorders')` says whether this Arcade can
|
|
440
|
+
paint borders at all, never whether it is painting them now.
|
|
441
|
+
- **It is safe at any time.** Before the connection exists your choice is held and sent when the
|
|
442
|
+
Arcade connects. A burst of calls — a theme picker previewing on hover — reaches the Arcade as
|
|
443
|
+
its final value, at most one a second, so it never trips a rate limit. An embedder that paints
|
|
444
|
+
no borders is not told, and nothing is raised.
|
|
445
|
+
- **The Arcade owns the change.** When and how the image swaps is the Arcade's; you get no
|
|
446
|
+
acknowledgement and no say in the timing. A game that restores a saved theme just after it
|
|
447
|
+
starts shows the default for a moment, then its own.
|
|
448
|
+
- **It shows only while your game is playing** — not before play starts, and not while your key
|
|
449
|
+
art covers the play area.
|
|
450
|
+
- **A string that is not a valid name is refused on your side**, with a console warning, and never
|
|
451
|
+
sent. The SDK does not trim it, lower-case it or shorten it.
|
|
452
|
+
- **A valid name your page does not declare is ignored**, and the Arcade keeps what it is showing.
|
|
453
|
+
On the dev and test channels the Arcade's own page logs a console warning naming the value, the
|
|
454
|
+
channel whose page it looked in, and the names that page declares; on release it says nothing.
|
|
455
|
+
|
|
456
|
+
### Publishing order and experiments
|
|
457
|
+
|
|
458
|
+
- **Publish the store page before, or together with, the build that uses a new name.** A build
|
|
459
|
+
asking for a name its page does not carry yet keeps the current image until the page arrives.
|
|
460
|
+
Rolling a build back is harmless; rolling the page back can remove a name, and the Arcade then
|
|
461
|
+
keeps showing what it has.
|
|
462
|
+
- **Every arm of a store-page experiment must carry every name your build uses.** The set comes
|
|
463
|
+
from the page the player opened, so an arm without a name falls under the rule above.
|
|
464
|
+
- **The borders come from the store page on screen.** To see your dev-channel borders, open your
|
|
465
|
+
game's store page on the dev channel. `gemctl serve` does not letterbox and shows no borders.
|
|
466
|
+
|
|
356
467
|
## An achievement unlock — celebrate it yourself, or declare the Arcade's
|
|
357
468
|
|
|
358
469
|
By default the Arcade draws no card when a player unlocks one of your game's
|
|
@@ -429,8 +540,10 @@ credit for it until you say so.
|
|
|
429
540
|
entry fields few and ordinary — and never gate first play behind one. A player made to type on a
|
|
430
541
|
remote before they may play mostly does not.
|
|
431
542
|
- **A TV performance budget.** TV-browser SoCs are weak next to any desktop: ship a player-settable
|
|
432
|
-
render-scale option (render at DPR 1 and upscale), bound memory, assume a fixed 16:9
|
|
433
|
-
|
|
543
|
+
render-scale option (render at DPR 1 and upscale), bound memory, assume a fixed frame — 16:9, or
|
|
544
|
+
a centred 9:16 column when your store page makes you
|
|
545
|
+
[a letterboxed portrait game](#art-beside-a-portrait-game-on-a-wide-screen-portrait-borders) —
|
|
546
|
+
with no `orientationchange` rescue coming, and keep your heights `dvh`-safe.
|
|
434
547
|
- **Reduced motion.** TV browsers rarely expose `prefers-reduced-motion`, so the pushed
|
|
435
548
|
`gem.prefs.reducedMotion` is the only signal a TV player has; honor it. It arrives with
|
|
436
549
|
[the prefs the parent pushes](#what-the-parent-says-about-the-session-prefs-pause-and-terminate)
|
|
@@ -1016,7 +1129,15 @@ renderer serves all of them.
|
|
|
1016
1129
|
| `displayTag` | The precomposed `Name#0001` | `PlayerText` |
|
|
1017
1130
|
| `avatarIconId` | The portrait item's id | `string` |
|
|
1018
1131
|
| `avatarIconUrl` | The portrait's thumbnail | `string` |
|
|
1019
|
-
| `badges` | Equipped badges in
|
|
1132
|
+
| `badges` | Equipped badges, at most two, in the player's order, each `{ itemId, iconUrl }` | array |
|
|
1133
|
+
|
|
1134
|
+
**A player chooses what they wear**: a portrait and up to two badges, each an icon they own — one
|
|
1135
|
+
an achievement in your game or any other may have awarded them ([achievements.md](achievements.md)
|
|
1136
|
+
§1). The portrait arrives as `avatarIconId` and `avatarIconUrl`, and the badges as `badges`, in the
|
|
1137
|
+
order the player put them in. `badges` is always a list — empty for a player who wears none, and on
|
|
1138
|
+
a row whose identity is withheld — so draw what is there, never pad it to two, and key on `itemId`,
|
|
1139
|
+
never on a position. A player can also equip a title, and colours and animations for their name and
|
|
1140
|
+
title. The SDK's identity does not carry those, so draw the name in your own style.
|
|
1020
1141
|
|
|
1021
1142
|
**The name and the tag are player-authored, so both are `PlayerText`** on every surface, not only
|
|
1022
1143
|
some. Convert with `text.toPlain()` where you render, never before.
|
package/package.json
CHANGED
package/schema/bridge.v1.json
CHANGED
|
@@ -590,7 +590,7 @@
|
|
|
590
590
|
"kind": "array",
|
|
591
591
|
"maxItems": 2,
|
|
592
592
|
"optional": true,
|
|
593
|
-
"$comment": "Equipped badges in
|
|
593
|
+
"$comment": "Equipped badges in the order the player arranged them — never more than two, and empty when the player wears none. Each is an item id and a thumbnail URL, the same pin caveat as `avatarIconUrl`.",
|
|
594
594
|
"items": {
|
|
595
595
|
"kind": "object",
|
|
596
596
|
"fields": {
|
|
@@ -2715,6 +2715,47 @@
|
|
|
2715
2715
|
"rateLimit": { "perMinute": 120 }
|
|
2716
2716
|
},
|
|
2717
2717
|
|
|
2718
|
+
{
|
|
2719
|
+
"type": "gem.portraitBorders",
|
|
2720
|
+
"direction": "frameToParent",
|
|
2721
|
+
"transport": "port",
|
|
2722
|
+
"$comment": [
|
|
2723
|
+
"Which of the store page's portrait borders the parent paints beside the",
|
|
2724
|
+
"game while it letterboxes a portrait game on a wide screen. `name` is one",
|
|
2725
|
+
"of the names the page declares under `media.portrait_borders`; absent",
|
|
2726
|
+
"means clear the sides to the parent's own bars, for a theme with no art.",
|
|
2727
|
+
"",
|
|
2728
|
+
"A KEY INTO PUBLISHED ART, NEVER THE ART. The images are the store page's,",
|
|
2729
|
+
"served and reviewed like every other store image; the frame chooses only",
|
|
2730
|
+
"which one and when. The parent looks `name` up by exact match in the list",
|
|
2731
|
+
"the page it is showing declares, and never renders it as text or builds a",
|
|
2732
|
+
"URL from it. A name that list does not hold is ignored and the current",
|
|
2733
|
+
"image stays. Transitions, their timing and any minimum dwell are the",
|
|
2734
|
+
"parent's, swaps wait while a trusted overlay is up, and the frame gets no",
|
|
2735
|
+
"acknowledgement.",
|
|
2736
|
+
"",
|
|
2737
|
+
"`name` is a plain bounded string with no format: a border name may carry",
|
|
2738
|
+
"digits and hyphens, which the `code` format refuses. The SDK sends only",
|
|
2739
|
+
"names matching `^[a-z0-9][a-z0-9_-]{0,39}$`, the store page's own rule, and",
|
|
2740
|
+
"a parent checks the same rule on receipt.",
|
|
2741
|
+
"",
|
|
2742
|
+
"STATE, NOT A MOMENT. The SDK holds the game's latest choice until the",
|
|
2743
|
+
"bridge connects and sends at most one a second, latest wins, so a burst",
|
|
2744
|
+
"(a theme picker previewing on hover) delivers its final value well inside",
|
|
2745
|
+
"the limit below.",
|
|
2746
|
+
"",
|
|
2747
|
+
"Gated by `portraitBorders`, which a parent that paints borders advertises",
|
|
2748
|
+
"to EVERY game on every device, whether or not the game declares borders or",
|
|
2749
|
+
"is letterboxed right now. It is never a signal of form factor or letterbox",
|
|
2750
|
+
"state, and the parent answers nothing, so the frame learns nothing about",
|
|
2751
|
+
"how it is being shown."
|
|
2752
|
+
],
|
|
2753
|
+
"fields": {
|
|
2754
|
+
"name": { "kind": "string", "maxLength": 40, "optional": true }
|
|
2755
|
+
},
|
|
2756
|
+
"rateLimit": { "perMinute": 60 }
|
|
2757
|
+
},
|
|
2758
|
+
|
|
2718
2759
|
{
|
|
2719
2760
|
"type": "gem.error",
|
|
2720
2761
|
"direction": "both",
|