@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.
@@ -811,10 +811,18 @@
811
811
  "A game may ignore it entirely.",
812
812
  "",
813
813
  "OPTIONAL because it is additive: a parent that predates it sends the",
814
- "fields above without it, and the SDK reports `null`. All five fields",
815
- "come together or the object is absent -- a parent that keeps only some",
816
- "of them fills the rest from the defaults below rather than sending a",
817
- "partial object.",
814
+ "fields above without it, and the SDK reports `null`. The first five",
815
+ "fields come together or the object is absent -- a parent that keeps",
816
+ "only some of them fills the rest from the defaults below rather than",
817
+ "sending a partial object.",
818
+ "",
819
+ "THE CAMERA AND CONTROLS FOUR ARE OPTIONAL INSIDE IT (owner decision",
820
+ "2026-10-11): `lookSensitivity`, `gamepadLookSensitivity`, `invertLookY`",
821
+ "and `touchMovement`, Roblox's per-device camera settings. A parent built",
822
+ "against an older SDK sends the object without them, and the SDK fills",
823
+ "each missing one with its default (100, 100, false, `floating`), so on",
824
+ "the game side all four are present whenever the object is. A game says",
825
+ "which of them it leaves to the player with `gem.controlsDeclare`.",
818
826
  "",
819
827
  "`muted`, `reducedMotion` and `showFrameStats` stay where they are, at",
820
828
  "the top of this message, and keep their meaning: they are the Arcade's",
@@ -829,8 +837,8 @@
829
837
  "WIDENING AN ENUM HERE IS NOT FREE. The generated validator refuses the",
830
838
  "WHOLE message on any field it rejects, so a value an older frame does",
831
839
  "not know would cost it `muted` and `reducedMotion` as well. A new",
832
- "keyboard layout or confirm button needs a capability the frame",
833
- "advertises before a parent may send it."
840
+ "keyboard layout, confirm button or touch-movement mode needs a",
841
+ "capability the frame advertises before a parent may send it."
834
842
  ],
835
843
  "fields": {
836
844
  "volume": {
@@ -856,6 +864,42 @@
856
864
  "vibration": {
857
865
  "kind": "boolean",
858
866
  "$comment": "Whether the player wants vibration and controller rumble. Default true."
867
+ },
868
+ "lookSensitivity": {
869
+ "kind": "int",
870
+ "min": 20,
871
+ "max": 400,
872
+ "optional": true,
873
+ "$comment": "Look speed for touch, mouse and pen, as a percentage of the base speeds (Roblox's MouseSensitivity): 100 is 1x, 20..400. Default 100 when absent. The Arcade's slider has eleven positions (20, 36, 52, 68, 84, 100, 160, 220, 280, 340, 400) but any integer in range is valid."
874
+ },
875
+ "gamepadLookSensitivity": {
876
+ "kind": "int",
877
+ "min": 20,
878
+ "max": 400,
879
+ "optional": true,
880
+ "$comment": "Look speed for a gamepad stick, as a percentage of the base speed (Roblox's GamepadCameraSensitivity): 100 is 1x, 20..400. Default 100 when absent."
881
+ },
882
+ "invertLookY": {
883
+ "kind": "boolean",
884
+ "optional": true,
885
+ "$comment": "Up/down look inverted: pushing up or dragging up looks DOWN (Roblox's CameraYInverted). Flips pitch only, never yaw. Default false when absent."
886
+ },
887
+ "touchMovement": {
888
+ "kind": "string",
889
+ "maxLength": 16,
890
+ "format": "code",
891
+ "optional": true,
892
+ "$comment": [
893
+ "On a touch screen, the movement thumbstick: `floating` appears where",
894
+ "the thumb lands (Roblox's Dynamic Thumbstick), `fixed` stays in one",
895
+ "place (Roblox's Thumbstick). Stored on every device, meaningful on",
896
+ "touch. Default `floating` when absent.",
897
+ "",
898
+ "A CODE, NOT AN ENUM, so the vocabulary can grow without costing an",
899
+ "older frame the whole message (see WIDENING above): a parent sends",
900
+ "only `floating` or `fixed` today, and the SDK maps any other value to",
901
+ "the default, `floating`, so a game only ever sees those two."
902
+ ]
859
903
  }
860
904
  }
861
905
  }
@@ -2393,9 +2437,10 @@
2393
2437
  "$comment": [
2394
2438
  "A platform-currency reward is on its way — the game's SERVER has just",
2395
2439
  "moved (or is about to move) a progression mapped onto a platform currency,",
2396
- "and the game would like the arcade's wallet to show it landing: a pending",
2397
- "state now and, when the platform confirms the grant, the currency flying",
2398
- "from `origin` into the wallet.",
2440
+ "and the game would like the arcade to show it landing: the currency flying",
2441
+ "from `origin` into the arcade's chrome — in the Gem Arcade, into its Gem",
2442
+ "button at the frame's top-left — and the total moving when the platform",
2443
+ "confirms the grant.",
2399
2444
  "",
2400
2445
  "A PRESENTATION CUE, NOT A CLAIM. Nothing here is trusted as a number. The",
2401
2446
  "amount that lands and the player's new total reach the parent from gem-api",
@@ -2408,7 +2453,8 @@
2408
2453
  "`origin` is where the flight starts, in CSS px of the frame's own",
2409
2454
  "coordinate space — the same space `gem.viewport` speaks — measured from",
2410
2455
  "the frame's top-left corner; the parent translates it into its own page.",
2411
- "Absent, the arcade animates at the wallet alone. Integers, like",
2456
+ "Absent, the Gem Arcade flies the confirmed grant from the middle of the",
2457
+ "frame, as it does a grant no cue announced. Integers, like",
2412
2458
  "`gem.viewport`'s insets: a fractional pixel is not a placement, and the",
2413
2459
  "SDK rounds and clamps on the way out. 16384 is a bound, not a size.",
2414
2460
  "",
@@ -2428,10 +2474,9 @@
2428
2474
  "other, `gem.cueReward` returns false and the game does NOTHING. There is",
2429
2475
  "no fallback celebration: Gems display belongs to the arcade (the",
2430
2476
  "creator's ruling, 2026-09-24), so a game draws no Gems flight, counter or",
2431
- "celebration of its own. The same holds for Tickets (`ARCADE_TICKETS`): the",
2432
- "arcade's wallet shows Gems and Tickets side by side and flies a payout",
2433
- "into the one `currencySlug` names. `origin` is the centre of the game's icon for",
2434
- "that currency — its gem, or its ticket."
2477
+ "celebration of its own. The same holds for Tickets (`ARCADE_TICKETS`):",
2478
+ "`currencySlug` decides what flies and which total moves. `origin` is the",
2479
+ "centre of the game's icon for that currency — its gem, or its ticket."
2435
2480
  ],
2436
2481
  "fields": {
2437
2482
  "currencySlug": { "kind": "string", "maxLength": 32 },
@@ -2487,6 +2532,12 @@
2487
2532
  "docks its wallets beside the frame rather than over it, this changes",
2488
2533
  "nothing.",
2489
2534
  "",
2535
+ "THE GEM ARCADE DRAWS NO WALLET OVER A GAME (since 2026-10-11): the player",
2536
+ "sees their balances in its Gem menu and payouts fly into its Gem button, so",
2537
+ "it advertises this message, accepts it, and changes nothing on screen — no",
2538
+ "wallet is shown, popped up or reserved whatever the game says. The rules",
2539
+ "above are for a parent that does draw wallets over the frame.",
2540
+ "",
2490
2541
  "A PRESENTATION REQUEST ONLY. Not an authorization control, and it carries no",
2491
2542
  "numbers: the parent takes every figure it shows from the platform. A game",
2492
2543
  "never draws a platform balance itself and never implements a pending state.",
@@ -2511,6 +2562,83 @@
2511
2562
  "rateLimit": { "perMinute": 60 }
2512
2563
  },
2513
2564
 
2565
+ {
2566
+ "type": "gem.controlsDeclare",
2567
+ "direction": "frameToParent",
2568
+ "transport": "port",
2569
+ "$comment": [
2570
+ "Which of the player's camera and controls settings this game leaves to",
2571
+ "the player and which it decides itself — Roblox's UserChoice versus",
2572
+ "`StarterPlayer.Dev*`, declared at runtime (owner decision 2026-10-11).",
2573
+ "The settings are the four camera and controls fields of",
2574
+ "`gem.prefs.recommended`; the parent's Settings panel shows each row",
2575
+ "according to this.",
2576
+ "",
2577
+ " 'player' the game applies the Arcade's value (or the player's own",
2578
+ " in-game choice, which wins); a normal row",
2579
+ " 'game' the game decides this itself; the parent shows the row",
2580
+ " disabled, \"Set by this game\"",
2581
+ " absent the game does not use this setting at all",
2582
+ "",
2583
+ "SCREEN STATE, NOT A TOGGLE, like `gem.walletDisplay`. Each message is the",
2584
+ "whole answer and the latest one wins; nothing accumulates. The SDK holds",
2585
+ "the latest declaration and re-sends it after every handshake, deduped per",
2586
+ "connection, so a parent that reconnects never shows a stale panel. With no",
2587
+ "message at all, the parent treats every setting as unused by this game.",
2588
+ "A declaration is per game session: the parent forgets it when the game is",
2589
+ "left.",
2590
+ "",
2591
+ "EACH VALUE IS A CODE, NOT AN ENUM, and an unknown KEY is ignored, so the",
2592
+ "vocabulary can grow without an older parent refusing the whole message:",
2593
+ "the SDK drops any key or value it does not know before sending, and a",
2594
+ "parent ignores any it does not know on receipt — treating that setting",
2595
+ "as absent.",
2596
+ "",
2597
+ "A PRESENTATION REQUEST ONLY. Not an authorization control: it changes",
2598
+ "which rows of the parent's own panel are editable, and nothing the game",
2599
+ "receives.",
2600
+ "",
2601
+ "Gated by `controls`: sent only to a parent that advertised it."
2602
+ ],
2603
+ "fields": {
2604
+ "uses": {
2605
+ "kind": "object",
2606
+ "$comment": "One optional entry per setting, each `player` or `game`. An empty object is a valid declaration: the game uses none of them.",
2607
+ "fields": {
2608
+ "lookSensitivity": {
2609
+ "kind": "string",
2610
+ "maxLength": 16,
2611
+ "format": "code",
2612
+ "optional": true,
2613
+ "$comment": "`player` or `game` for `gem.prefs.recommended.lookSensitivity`."
2614
+ },
2615
+ "gamepadLookSensitivity": {
2616
+ "kind": "string",
2617
+ "maxLength": 16,
2618
+ "format": "code",
2619
+ "optional": true,
2620
+ "$comment": "`player` or `game` for `gem.prefs.recommended.gamepadLookSensitivity`."
2621
+ },
2622
+ "invertLookY": {
2623
+ "kind": "string",
2624
+ "maxLength": 16,
2625
+ "format": "code",
2626
+ "optional": true,
2627
+ "$comment": "`player` or `game` for `gem.prefs.recommended.invertLookY`."
2628
+ },
2629
+ "touchMovement": {
2630
+ "kind": "string",
2631
+ "maxLength": 16,
2632
+ "format": "code",
2633
+ "optional": true,
2634
+ "$comment": "`player` or `game` for `gem.prefs.recommended.touchMovement`."
2635
+ }
2636
+ }
2637
+ }
2638
+ },
2639
+ "rateLimit": { "perMinute": 60 }
2640
+ },
2641
+
2514
2642
  {
2515
2643
  "type": "gem.menu",
2516
2644
  "direction": "frameToParent",
@@ -218,6 +218,12 @@
218
218
  "gem.menu",
219
219
  "gem.menuSelect"
220
220
  ]
221
+ },
222
+ "controls": {
223
+ "implemented": true,
224
+ "gates": [
225
+ "gem.controlsDeclare"
226
+ ]
221
227
  }
222
228
  }
223
229
  }