@indiegems/gem-web-sdk 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -590,7 +590,7 @@
590
590
  "kind": "array",
591
591
  "maxItems": 2,
592
592
  "optional": true,
593
- "$comment": "Equipped badges in display order — empty until platform achievements equip any, and never more than two. Each is an item id and a thumbnail URL, the same pin caveat as `avatarIconUrl`.",
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",
@@ -3162,6 +3203,9 @@
3162
3203
  "",
3163
3204
  " RENDER IT IN YOUR OWN CHROME. Labelled as a promotion, dismissible, and",
3164
3205
  " focus-trapped over the paused game. Nothing in this message is rendered.",
3206
+ " For `rewarded`, the player pressed the game's button, and the game's",
3207
+ " label is not the disclosure: paint an offer sheet that says it is an ad",
3208
+ " and what the player gets, with a way to decline, BEFORE anything plays.",
3165
3209
  "",
3166
3210
  " PAUSE WITH `gem.pause`. That message already means 'stop running and stop",
3167
3211
  " making noise', which is exactly what is wanted while a panel is up. Do",
@@ -3183,10 +3227,14 @@
3183
3227
  " generated validator checks type and length only - it may carry `../`,",
3184
3228
  " NUL, newlines, RTL overrides and astral Unicode. The SDK will only SEND",
3185
3229
  " `^[a-z0-9_-]{1,32}$`, which is the grammar the platform's own ad",
3186
- " endpoint accepts - but that is the sender keeping a promise, not a",
3187
- " guarantee you hold: a hostile frame posts on the port directly. It is",
3188
- " matched against nothing, never interpolated into a path or a log line,",
3189
- " and never shown to a player.",
3230
+ " endpoints accept - but that is the sender keeping a promise, not a",
3231
+ " guarantee you hold: a hostile frame posts on the port directly. For an",
3232
+ " interstitial it is matched against nothing. For `rewarded` it is the key",
3233
+ " of the reward the game's config declares, so check it against that",
3234
+ " grammar YOURSELF before forwarding it, in a request body, to the",
3235
+ " platform's rewarded offer. Never interpolate it into a path or an",
3236
+ " unchecked log line, never show it to a player, and NEVER SEND IT TO AN",
3237
+ " AD NETWORK: it stops at the platform, as every game-authored string does.",
3190
3238
  "",
3191
3239
  " THE `perMinute` BELOW BOUNDS A BUGGY GAME, NOT A HOSTILE ONE. A hostile",
3192
3240
  " game posts on the port it holds rather than calling the SDK, so a",
@@ -3202,12 +3250,12 @@
3202
3250
  "placement": {
3203
3251
  "kind": "string",
3204
3252
  "maxLength": 32,
3205
- "$comment": "The game's own name for the break it is at - 'level-complete', 'between-rounds'. UNTRUSTED TEXT, MATCHED AGAINST NOTHING BY TODAY'S PARENT: it does not look it up, does not vary its decision on it and does not render it. THE BOUND IS THE PLATFORM'S OWN, not a number chosen here: the ad endpoint behind the arcade accepts `^[a-z0-9_-]{1,32}$` and answers 400 otherwise, so 32 is what this carries and the SDK refuses the charset before sending. A real ad network keys its ad units BY placement - that is what the field is for - so the day one is swapped in the parent starts forwarding this string unchanged, and a game that shipped 'Boss Fight' or a 40-character name would have to be rebuilt. The grammar is narrow NOW, while nothing has shipped against it, so a game learns at development time instead. Codegen emits type and length only; the charset is the sender's to keep."
3253
+ "$comment": "The game's own name for the break it is at - 'level-complete', 'classic_revive'. UNTRUSTED TEXT, never rendered. FOR AN INTERSTITIAL IT IS MATCHED AGAINST NOTHING: the parent does not look it up and does not vary its decision on it. FOR `rewarded` IT NAMES THE REWARD the game's config declares in `rewarded_placements`: the parent forwards it unchanged to the platform's rewarded offer, which matches it against that config, and a placement the config does not declare is answered `no_ad`. IT NEVER REACHES AN AD NETWORK, for either format, because a game-authored string that fits this grammar fits a dashless UUID too and would be an identifier channel to a third party. THE BOUND IS THE PLATFORM'S OWN, not a number chosen here: the platform's ad endpoints accept `^[a-z0-9_-]{1,32}$` and answer 400 otherwise, and config upload holds a declared placement to the same grammar, so 32 is what this carries and the SDK refuses the charset before sending. A game that shipped 'Boss Fight' or a 40-character name would have to be rebuilt to reach its reward, so the SDK refuses it at development time instead. Codegen emits type and length only; the charset is the sender's to keep."
3206
3254
  },
3207
3255
  "format": {
3208
3256
  "kind": "enum",
3209
3257
  "values": ["interstitial", "rewarded"],
3210
- "$comment": "A closed set, so a typo is refused at the boundary rather than left for the parent to interpret. `rewarded` IS IN THE GRAMMAR AND IS NOT SERVED: it is answered `no_ad` today, and a `shown` would grant nothing even when it is not - see gem.adResult's outcome. A game that grants a reward on an outcome has a reward any page that can post on this port can forge."
3258
+ "$comment": "A closed set, so a typo is refused at the boundary rather than left for the parent to interpret. `rewarded` IS SERVED WHERE THE PARENT SERVES IT, and answered `no_ad` where it does not. Either way a `shown` grants nothing - see gem.adResult's outcome: the platform grants a rewarded placement's reward itself, into the game's own inventory, against its own record. A game that grants a reward on an outcome has a reward any page that can post on this port can forge."
3211
3259
  }
3212
3260
  },
3213
3261
  "rateLimit": {
@@ -3236,6 +3284,23 @@
3236
3284
  " unavailable - the shell could not render at all just now. NOT a softer",
3237
3285
  " no; a game reads it identically to `no_ad`.",
3238
3286
  "",
3287
+ "AFTER A `rewarded` ASK the player pressed a button, so two of those",
3288
+ "readings change. `shown` means an offer sheet was painted and has",
3289
+ "closed, whatever the player chose inside it, and still grants nothing.",
3290
+ "`no_ad` can arrive with the reward already granted - where no ad serves",
3291
+ "and the game's config says to grant without one, for one - so a game",
3292
+ "reads its own inventory, never the outcome, to learn whether a reward",
3293
+ "landed. And where nothing new landed, a neutral 'no reward this time'",
3294
+ "line from the game is right, because the player is waiting on their own",
3295
+ "press; it must never guess a reason.",
3296
+ "",
3297
+ "THAT INVENTORY IS A SECOND CHANNEL, AND THE RULE BELOW DOES NOT CLOSE IT.",
3298
+ "The rule closes the outcome. Whether a reward landed is visible to the",
3299
+ "game by design, so which `no_ad` branches grant one is a disclosure the",
3300
+ "platform decides where it makes the grant. A parent adds nothing to it:",
3301
+ "it never grants, and it answers a rewarded ask exactly as below whatever",
3302
+ "was granted.",
3303
+ "",
3239
3304
  "THREE VALUES, AND THAT IS THE COMPLETE VOCABULARY. `no_ad` absorbs the",
3240
3305
  "feature being switched off, an empty catalogue once the running game is",
3241
3306
  "excluded, a frequency cap that has not expired, and a player whose",
@@ -123,6 +123,12 @@
123
123
  "gem.frameStats"
124
124
  ]
125
125
  },
126
+ "portraitBorders": {
127
+ "implemented": true,
128
+ "gates": [
129
+ "gem.portraitBorders"
130
+ ]
131
+ },
126
132
  "engagement": {
127
133
  "implemented": true,
128
134
  "gates": [