@bountyboard/arcade-sdk 1.2.0 → 1.3.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  BBArcade
3
- } from "./chunk-KDBBR532.js";
3
+ } from "./chunk-PYW5F45R.js";
4
4
 
5
5
  // src/multiplayer.ts
6
6
  var WELCOME_TIMEOUT_MS = 1e4;
@@ -42,6 +42,46 @@ interface BBArcadeRewardedAdsConfig {
42
42
  interface BBArcadeConfig {
43
43
  rewardedAds?: BBArcadeRewardedAdsConfig;
44
44
  }
45
+ /**
46
+ * What is backing `window.localStorage` right now.
47
+ * - `native` — a real Storage works here; the SDK changed nothing.
48
+ * - `cloud` — the shim is installed and syncs to the player's Bounty Board save.
49
+ * - `memory` — the shim is installed but nothing can persist (guest, standalone,
50
+ * off-host); reads and writes work for the session and are then discarded.
51
+ */
52
+ type BBArcadeStorageMode = 'native' | 'cloud' | 'memory';
53
+ /**
54
+ * localStorage compatibility for hosted builds. Bounty-Board-hosted uploads run
55
+ * in an opaque-origin sandbox where `localStorage` THROWS rather than returning
56
+ * empty, which breaks engine exports (GameMaker `ini_*`, Godot, Unity) that
57
+ * assume synchronous storage. The shim replaces it with a Storage-shaped object
58
+ * backed by the SDK's own cloud save, so those builds persist per player and
59
+ * across devices with no engine changes.
60
+ *
61
+ * The script-tag build installs it automatically at load. Module consumers keep
62
+ * a side-effect-free import and call `install()` themselves before boot.
63
+ */
64
+ interface BBArcadeStorage {
65
+ /**
66
+ * Install the shim if this origin has no working localStorage, and return the
67
+ * resulting mode. Idempotent, and a no-op when native storage works. Call it
68
+ * BEFORE the engine/game script runs.
69
+ */
70
+ install(): BBArcadeStorageMode;
71
+ /**
72
+ * Resolves once the cloud read has landed (or settled as unpersistable).
73
+ * `getItem` is synchronous but the cloud read is not, so a game that reads
74
+ * saved progress during boot should await this first.
75
+ */
76
+ ready(): Promise<BBArcadeStorageMode>;
77
+ /**
78
+ * Force pending writes out now instead of waiting for the debounce. Rejects
79
+ * with the same BBArcadeError codes as save().
80
+ */
81
+ flush(): Promise<void>;
82
+ /** The current backing mode. */
83
+ readonly mode: BBArcadeStorageMode;
84
+ }
45
85
  /**
46
86
  * The logged-in player's public display identity, as delivered by the Bounty
47
87
  * Board host. Display name + avatar ONLY — the host never sends ids, emails,
@@ -402,6 +442,11 @@ interface BBArcadeSDK {
402
442
  * 'error' after the same 15s request timeout when no host answers).
403
443
  */
404
444
  load(): Promise<string | null>;
445
+ /**
446
+ * localStorage compatibility for hosted builds — the escape hatch for engine
447
+ * exports that can't be rewired to call save()/load(). See BBArcadeStorage.
448
+ */
449
+ storage: BBArcadeStorage;
405
450
  /**
406
451
  * The logged-in player's display identity for in-game UI. Resolves
407
452
  * { name, avatarUrl } once the host's config handshake completes (or after
@@ -436,4 +481,4 @@ interface BBArcadeSDK {
436
481
  version: number;
437
482
  }
438
483
 
439
- export type { BBArcadeSDK as B, BBArcadeConfig as a, BBArcadeError as b, BBArcadeErrorCode as c, BBArcadeLockToHostOptions as d, BBArcadePlayer as e, BBArcadePrepareRewardedAdOptions as f, BBArcadePreparedRewardedAd as g, BBArcadeRewardedAdOptions as h, BBArcadeRewardedAdPreparation as i, BBArcadeRewardedAdPrepareFailure as j, BBArcadeRewardedAdResult as k, BBArcadeRewardedAdStatus as l, BBArcadeRewardedAdsConfig as m, BBArcadeRewardedBreakOptions as n, BBArcadeMpJoinOptions as o, BBArcadeMpRoom as p, BBArcadeMultiplayer as q, BBArcadeMpPlayer as r, BBArcadeMpResult as s, BBArcadeMpRoomEvents as t };
484
+ export type { BBArcadeSDK as B, BBArcadeConfig as a, BBArcadeError as b, BBArcadeErrorCode as c, BBArcadeLockToHostOptions as d, BBArcadePlayer as e, BBArcadePrepareRewardedAdOptions as f, BBArcadePreparedRewardedAd as g, BBArcadeRewardedAdOptions as h, BBArcadeRewardedAdPreparation as i, BBArcadeRewardedAdPrepareFailure as j, BBArcadeRewardedAdResult as k, BBArcadeRewardedAdStatus as l, BBArcadeRewardedAdsConfig as m, BBArcadeRewardedBreakOptions as n, BBArcadeStorage as o, BBArcadeStorageMode as p, BBArcadeMpJoinOptions as q, BBArcadeMpRoom as r, BBArcadeMultiplayer as s, BBArcadeMpPlayer as t, BBArcadeMpResult as u, BBArcadeMpRoomEvents as v };
@@ -42,6 +42,46 @@ interface BBArcadeRewardedAdsConfig {
42
42
  interface BBArcadeConfig {
43
43
  rewardedAds?: BBArcadeRewardedAdsConfig;
44
44
  }
45
+ /**
46
+ * What is backing `window.localStorage` right now.
47
+ * - `native` — a real Storage works here; the SDK changed nothing.
48
+ * - `cloud` — the shim is installed and syncs to the player's Bounty Board save.
49
+ * - `memory` — the shim is installed but nothing can persist (guest, standalone,
50
+ * off-host); reads and writes work for the session and are then discarded.
51
+ */
52
+ type BBArcadeStorageMode = 'native' | 'cloud' | 'memory';
53
+ /**
54
+ * localStorage compatibility for hosted builds. Bounty-Board-hosted uploads run
55
+ * in an opaque-origin sandbox where `localStorage` THROWS rather than returning
56
+ * empty, which breaks engine exports (GameMaker `ini_*`, Godot, Unity) that
57
+ * assume synchronous storage. The shim replaces it with a Storage-shaped object
58
+ * backed by the SDK's own cloud save, so those builds persist per player and
59
+ * across devices with no engine changes.
60
+ *
61
+ * The script-tag build installs it automatically at load. Module consumers keep
62
+ * a side-effect-free import and call `install()` themselves before boot.
63
+ */
64
+ interface BBArcadeStorage {
65
+ /**
66
+ * Install the shim if this origin has no working localStorage, and return the
67
+ * resulting mode. Idempotent, and a no-op when native storage works. Call it
68
+ * BEFORE the engine/game script runs.
69
+ */
70
+ install(): BBArcadeStorageMode;
71
+ /**
72
+ * Resolves once the cloud read has landed (or settled as unpersistable).
73
+ * `getItem` is synchronous but the cloud read is not, so a game that reads
74
+ * saved progress during boot should await this first.
75
+ */
76
+ ready(): Promise<BBArcadeStorageMode>;
77
+ /**
78
+ * Force pending writes out now instead of waiting for the debounce. Rejects
79
+ * with the same BBArcadeError codes as save().
80
+ */
81
+ flush(): Promise<void>;
82
+ /** The current backing mode. */
83
+ readonly mode: BBArcadeStorageMode;
84
+ }
45
85
  /**
46
86
  * The logged-in player's public display identity, as delivered by the Bounty
47
87
  * Board host. Display name + avatar ONLY — the host never sends ids, emails,
@@ -402,6 +442,11 @@ interface BBArcadeSDK {
402
442
  * 'error' after the same 15s request timeout when no host answers).
403
443
  */
404
444
  load(): Promise<string | null>;
445
+ /**
446
+ * localStorage compatibility for hosted builds — the escape hatch for engine
447
+ * exports that can't be rewired to call save()/load(). See BBArcadeStorage.
448
+ */
449
+ storage: BBArcadeStorage;
405
450
  /**
406
451
  * The logged-in player's display identity for in-game UI. Resolves
407
452
  * { name, avatarUrl } once the host's config handshake completes (or after
@@ -436,4 +481,4 @@ interface BBArcadeSDK {
436
481
  version: number;
437
482
  }
438
483
 
439
- export type { BBArcadeSDK as B, BBArcadeConfig as a, BBArcadeError as b, BBArcadeErrorCode as c, BBArcadeLockToHostOptions as d, BBArcadePlayer as e, BBArcadePrepareRewardedAdOptions as f, BBArcadePreparedRewardedAd as g, BBArcadeRewardedAdOptions as h, BBArcadeRewardedAdPreparation as i, BBArcadeRewardedAdPrepareFailure as j, BBArcadeRewardedAdResult as k, BBArcadeRewardedAdStatus as l, BBArcadeRewardedAdsConfig as m, BBArcadeRewardedBreakOptions as n, BBArcadeMpJoinOptions as o, BBArcadeMpRoom as p, BBArcadeMultiplayer as q, BBArcadeMpPlayer as r, BBArcadeMpResult as s, BBArcadeMpRoomEvents as t };
484
+ export type { BBArcadeSDK as B, BBArcadeConfig as a, BBArcadeError as b, BBArcadeErrorCode as c, BBArcadeLockToHostOptions as d, BBArcadePlayer as e, BBArcadePrepareRewardedAdOptions as f, BBArcadePreparedRewardedAd as g, BBArcadeRewardedAdOptions as h, BBArcadeRewardedAdPreparation as i, BBArcadeRewardedAdPrepareFailure as j, BBArcadeRewardedAdResult as k, BBArcadeRewardedAdStatus as l, BBArcadeRewardedAdsConfig as m, BBArcadeRewardedBreakOptions as n, BBArcadeStorage as o, BBArcadeStorageMode as p, BBArcadeMpJoinOptions as q, BBArcadeMpRoom as r, BBArcadeMultiplayer as s, BBArcadeMpPlayer as t, BBArcadeMpResult as u, BBArcadeMpRoomEvents as v };
package/docs/llms.txt CHANGED
@@ -2,20 +2,17 @@
2
2
 
3
3
  Human guide: https://www.bountyboard.gg/arcade/sdk
4
4
  Package: @bountyboard/arcade-sdk
5
- Stable npm release: 1.1.0
6
- Current browser v1/source addition: public quick match (queued for npm 1.2.0)
5
+ Stable npm release: 1.2.0
7
6
  Wire protocol: 1
8
7
 
9
8
  This file is the complete integration contract for coding agents integrating an
10
9
  HTML5 game. The package TypeScript declarations remain the exact public type
11
- reference. Version availability matters: npm 1.1.0 supports code/create rooms
12
- on all three approved rails: Bounty shared-service referee modules, Bounty
13
- shared-service relay rooms, and host-routed registered external authorities.
14
- match: true works on both Bounty shared-service tiers in the current
15
- /arcade-sdk/v1.js browser artifact and repository source, and will enter the
16
- npm package in 1.2.0. BBArcadeError.detail follows that same version gate. The
17
- external-authority adapter guide is public now and will also join the packaged
18
- docs in 1.2.0.
10
+ reference. npm 1.2.0, the /arcade-sdk/v1.js browser artifact, and repository
11
+ source all expose the same surface. Rooms open on all three approved rails:
12
+ Bounty shared-service referee modules, Bounty shared-service relay rooms, and
13
+ host-routed registered external authorities. code/create works on every rail;
14
+ match: true works on both Bounty shared-service tiers. The external-authority
15
+ adapter guide ships in the package and is public at the URL above.
19
16
 
20
17
  ## Non-negotiable integration rules
21
18
 
@@ -29,7 +26,9 @@ docs in 1.2.0.
29
26
  4. getPlayer() can be null and avatarUrl can be null. The SDK never exposes
30
27
  account ids, emails, or roles.
31
28
  5. Store all progress in one save(string) blob (about 1 MB). Save at
32
- checkpoints/game over, not in a frame loop, and catch every rejection.
29
+ checkpoints/game over, not in a frame loop, and catch every rejection. When
30
+ the game is an engine export whose storage layer you cannot rewire, use
31
+ storage.install() instead and let the shim carry localStorage to the cloud.
33
32
  6. For new rewarded-ad work, prepare first, enable the game's button only when
34
33
  status is ready, call prepared.show() directly from the click/tap handler,
35
34
  and grant only when the final status is viewed.
@@ -38,7 +37,10 @@ docs in 1.2.0.
38
37
  admission, roster, capacity, host succession, reconnect grace, and public
39
38
  message fan-out, but it does not referee game state or outcomes. Catch every
40
39
  joinRoom() rejection and keep solo/standalone play available.
41
- 8. lockToHost() is an optional anti-rehosting deterrent, not DRM. Call it before boot.
40
+ 8. lockToHost() is an optional anti-rehosting deterrent, not DRM. Call it before
41
+ boot. allow lists the domains the STUDIO hosts the build on, so a build that
42
+ only runs on Bounty Board passes no allow entries (or skips the call);
43
+ play.yourgame.com in every example is a placeholder, never a real value.
42
44
 
43
45
  ## Install and distribution
44
46
 
@@ -81,9 +83,11 @@ Distribution format does not determine capabilities; the embedding host does.
81
83
 
82
84
  - Bounty-hosted upload:
83
85
  lifecycle/scores supported; player identity/variants supported; cloud
84
- save/load available to logged-in players; ads available to approved games;
85
- multiplayer available after per-game approval, with relay as the default
86
- Bounty shared-service tier when no bespoke referee module is registered.
86
+ save/load available to logged-in players; native localStorage/sessionStorage/
87
+ IndexedDB/cookies all THROW (opaque origin), so engine exports need
88
+ storage.install(); ads available to approved games; multiplayer available
89
+ after per-game approval, with relay as the default Bounty shared-service tier
90
+ when no bespoke referee module is registered.
87
91
  - Approved external URL embed inside the Bounty Board player:
88
92
  lifecycle/scores, identity, variants, and approved multiplayer (including the
89
93
  Bounty relay tier) are supported; rewarded ads are unavailable (no payable
@@ -97,11 +101,19 @@ Distribution format does not determine capabilities; the embedding host does.
97
101
  Guests are normal. getPlayer resolves null and account-backed calls may reject
98
102
  with code unauthenticated.
99
103
 
104
+ Leaderboards need one thing outside the code: the studio must declare
105
+ Leaderboards for the game in its Arcade submission (or later by editing it).
106
+ Until then the host rejects every posted score and the game sees nothing,
107
+ because submitScore()/gameOver() are fire-and-forget. Optional per-game score
108
+ ceilings live beside that declaration and reject implausible values.
109
+
100
110
  ## Minimal correct lifecycle
101
111
 
102
112
  import { BBArcade } from '@bountyboard/arcade-sdk';
103
113
 
104
- BBArcade.lockToHost({ allow: ['play.yourgame.com'] }); // optional, before boot
114
+ // Optional, before boot. Bounty Board and localhost are always allowed, so
115
+ // allow lists only domains YOU host on; omit it when Bounty Board hosts the build.
116
+ BBArcade.lockToHost({ allow: ['play.yourgame.com'] }); // placeholder domain
105
117
  void BBArcade.init(); // never gate boot on it
106
118
  BBArcade.gameLoadingFinished(); // first playable scene ready
107
119
 
@@ -114,7 +126,7 @@ For a shared-seed Daily, pass { mode: 'daily' } to submitScore and gameOver.
114
126
 
115
127
  ## Exact shared types and configuration
116
128
 
117
- All rejected SDK promises use a real Error. Stable npm 1.1.0 has this shape:
129
+ All rejected SDK promises use a real Error:
118
130
 
119
131
  type BBArcadeErrorCode =
120
132
  | 'unsupported' // no compatible host/capability
@@ -123,12 +135,6 @@ All rejected SDK promises use a real Error. Stable npm 1.1.0 has this shape:
123
135
  | 'rejected' // host/authority refused the payload or room
124
136
  | 'error'; // timeout, transport, or server failure
125
137
 
126
- interface BBArcadeError extends Error {
127
- code: BBArcadeErrorCode;
128
- }
129
-
130
- The current browser v1/source build, and npm 1.2.0 when released, add:
131
-
132
138
  interface BBArcadeError extends Error {
133
139
  code: BBArcadeErrorCode;
134
140
  detail?: string; // raw authority reason when available
@@ -167,6 +173,20 @@ Player and score shapes:
167
173
  interface BBArcadePlayer { name: string; avatarUrl: string | null }
168
174
  // submitScore/gameOver use the inline option shape { mode?: 'daily' }.
169
175
 
176
+ Storage compatibility shapes:
177
+
178
+ type BBArcadeStorageMode =
179
+ | 'native' // a real Storage works here; the SDK changed nothing
180
+ | 'cloud' // shim installed, syncing to the player's Bounty Board save
181
+ | 'memory'; // shim installed but nothing can persist (guest/standalone)
182
+
183
+ interface BBArcadeStorage {
184
+ install(): BBArcadeStorageMode; // idempotent; no-op when native works
185
+ ready(): Promise<BBArcadeStorageMode>;
186
+ flush(): Promise<void>; // rejects with the save() error codes
187
+ readonly mode: BBArcadeStorageMode;
188
+ }
189
+
170
190
  Rewarded-ad options and results:
171
191
 
172
192
  interface BBArcadeRewardedAdOptions {
@@ -264,9 +284,18 @@ Player data and experiments:
264
284
  against the same 1 MiB cap the server enforces.
265
285
  - load(): Promise<string | null>
266
286
  Returns null when no save exists. Rejects like save().
287
+ - storage: BBArcadeStorage
288
+ localStorage compatibility for hosted builds. install(): BBArcadeStorageMode
289
+ replaces a throwing window.localStorage with a Storage-shaped object backed
290
+ by the cloud save, and is a no-op where a real Storage works. ready():
291
+ Promise<BBArcadeStorageMode> resolves once the cloud read has landed.
292
+ flush(): Promise<void> forces the debounced write out now. mode is the
293
+ current BBArcadeStorageMode ('native' | 'cloud' | 'memory'). The script-tag
294
+ build calls install() for you at load; module consumers call it themselves
295
+ before boot.
267
296
  - getPlayer(): Promise<{ name: string, avatarUrl: string | null } | null>
268
297
  Display identity only; always handle null.
269
- - onPlayerChange(handler): () => void // current browser/source; npm 1.2.0 when released
298
+ - onPlayerChange(handler): () => void
270
299
  Subscribes to CHANGES in the display identity (mid-session login/logout).
271
300
  The handler receives the same { name, avatarUrl } | null shape as
272
301
  getPlayer() and runs only when the identity actually changes; subscribing
@@ -307,16 +336,16 @@ ad_break_unavailable, ad_break_timeout, no_rewarded_ad, ad_in_progress,
307
336
  show_ad_error, direct_user_action_required, before_reward_error, and
308
337
  ad_break_error.
309
338
 
310
- Multiplayer (current browser v1/source shape; stable npm 1.1.0 omits match):
339
+ Multiplayer:
311
340
 
312
341
  type BBArcadeMpJoinOptions = {
313
342
  code?: string;
314
343
  create?: boolean;
315
- match?: boolean; // current browser/source; npm 1.2.0 when released
344
+ match?: boolean; // public quick match; Bounty shared-service tiers only
316
345
  joinData?: Readonly<Record<string, unknown>>;
317
346
  roomUrl?: string; // local development override; provide with ticket
318
347
  ticket?: string; // local development override; provide with roomUrl
319
- timeoutMs?: number; // current browser/source; npm 1.2.0 when released
348
+ timeoutMs?: number; // welcome timeout override, clamped 1000-60000 ms
320
349
  };
321
350
 
322
351
  interface BBArcadeMpPlayer {
@@ -350,7 +379,7 @@ Multiplayer (current browser v1/source shape; stable npm 1.1.0 omits match):
350
379
  players: BBArcadeMpPlayer[];
351
380
  state: unknown;
352
381
  connected: boolean;
353
- latencyMs: number | null; // current browser/source; npm 1.2.0 when released
382
+ latencyMs: number | null; // join-handshake estimate, refreshed on reconnect
354
383
  send(input: unknown): void;
355
384
  trySend(input: unknown): boolean;
356
385
  on<K extends keyof BBArcadeMpRoomEvents>(
@@ -416,10 +445,9 @@ as unknown.
416
445
  - leave intentionally closes the Room and permanently disables reconnect for it.
417
446
  A welcome that arrives after leave() (e.g. racing a reconnect) is ignored and
418
447
  never flips the room back to connected.
419
- - A pre-welcome room error rejects joinRoom with BBArcadeError. In the current
420
- browser/source build and npm 1.2.0+, raw reasons such as room_full/room_over
421
- are carried in error.detail when available. Stable npm 1.1.0 exposes only
422
- error.code. An error after welcome emits room.on('error', { code }).
448
+ - A pre-welcome room error rejects joinRoom with BBArcadeError. Raw reasons such
449
+ as room_full/room_over are carried in error.detail when available.
450
+ An error after welcome emits room.on('error', { code }).
423
451
  Connection loss emits connection/close events and may start automatic
424
452
  reconnect.
425
453
 
@@ -429,9 +457,10 @@ version field on multiplayer WebSocket frames.
429
457
 
430
458
  ## Cloud-save recipe
431
459
 
432
- Hosted uploads have an opaque origin and no localStorage, so SDK save is the
433
- primary store there. URL embeds and standalone builds need their own same-origin
434
- fallback.
460
+ Hosted uploads have an opaque origin where a real localStorage is not merely
461
+ empty — reading window.localStorage THROWS a SecurityError, as do
462
+ sessionStorage, IndexedDB, and document.cookie. SDK save is the primary store
463
+ there. URL embeds and standalone builds need their own same-origin fallback.
435
464
 
436
465
  const blob = JSON.stringify(progress);
437
466
  try {
@@ -449,6 +478,59 @@ fallback.
449
478
 
450
479
  Do not assume load() resolves null for a guest; it can reject unauthenticated.
451
480
 
481
+ ## localStorage shim for engine exports
482
+
483
+ Prefer save()/load() when you control the source. The shim exists for engine
484
+ runtimes whose storage layer cannot be rewired without patching engine
485
+ internals: GameMaker HTML5 (ini_open/ini_write_*/game_save all sit on
486
+ localStorage), Godot, Unity, and Construct.
487
+
488
+ With the script tag, nothing is required beyond load order — the SDK installs
489
+ the shim at load and window.localStorage starts working:
490
+
491
+ <script src="https://www.bountyboard.gg/arcade-sdk/v1.js"></script>
492
+ <script src="html5game/YourGame.js"></script>
493
+
494
+ Module consumers keep a side-effect-free import and install it themselves,
495
+ before any engine code runs:
496
+
497
+ import { BBArcade } from '@bountyboard/arcade-sdk';
498
+ BBArcade.storage.install();
499
+
500
+ install() is idempotent and a no-op wherever a real Storage works, so standalone
501
+ play and URL embeds are untouched. Behavior once installed:
502
+
503
+ - Reads and writes are synchronous and in-memory; the map is persisted to the
504
+ player's cloud save on a short debounce, on pagehide, and on flush().
505
+ - Progress becomes per-player and cross-device, not per-browser.
506
+ - setItem throws QuotaExceededError when the write would exceed the ~1 MB save
507
+ cap, matching a real Storage.
508
+ - sessionStorage is shimmed too, but memory-only — it is per-session by
509
+ definition and is never synced.
510
+ - Guests and standalone play settle in 'memory' mode: storage still works for
511
+ the session, it is just never persisted. Nothing rejects; nothing hangs.
512
+
513
+ The one real limitation is that getItem is synchronous while the cloud read is
514
+ not. Gate boot-time reads on ready():
515
+
516
+ await BBArcade.storage.ready(); // 'cloud' | 'memory' | 'native'
517
+ startGame();
518
+
519
+ Writes made before hydration are kept and merged with the cloud read (the
520
+ game's own write wins, and a removeItem is not resurrected), and nothing is
521
+ pushed to the server until that read has SUCCEEDED. A failed read is retried on
522
+ the next write, and a write that still cannot confirm what the player had is
523
+ refused (rejecting with code error) rather than allowed to overwrite it blind —
524
+ so neither a fresh-start write at boot nor a transport blip can wipe an existing
525
+ save. A game that reads
526
+ at boot WITHOUT awaiting ready() may still see an empty map on the first frame.
527
+
528
+ save()/load() keep working alongside the shim: the player has one save slot, so
529
+ with the shim active both halves share it inside a tagged envelope. load()
530
+ unwraps it and returns only what save() wrote. A raw blob written before the
531
+ shim existed is read back untouched, so turning the shim on never orphans a
532
+ save.
533
+
452
534
  ## Safe rewarded-ad recipe
453
535
 
454
536
  Prepare at the natural break. Keep the game-owned button disabled until ready.
@@ -484,7 +566,7 @@ is normal even though test mode is enabled automatically.
484
566
 
485
567
  try {
486
568
  const room = await joinRoom({
487
- create: true, // or code: 'ABCD'; browser v1/npm 1.2 adds match: true
569
+ create: true, // or code: 'ABCD', or match: true for public quick match
488
570
  // Only a relay founder uses roomSize; every joinData field is untrusted.
489
571
  joinData: { roomSize: 4, avatar: 'golem' },
490
572
  });
@@ -603,9 +685,8 @@ described above; its 1 Hz snapshot contains metadata only.
603
685
  can roll before reaching the room's capacity. A matched room.code is also an
604
686
  invite code.
605
687
  - A lost last-seat/ended-room race triggers at most 2 re-matchmaking retries
606
- (3 total attempts). In the current browser/source build and npm 1.2.0+, final
607
- rejection has code rejected and detail room_full or room_over, whichever the
608
- last attempt returned.
688
+ (3 total attempts). Final rejection has code rejected and detail room_full or
689
+ room_over, whichever the last attempt returned.
609
690
  - When a finite Bounty referee module ends, it broadcasts match_end once, closes
610
691
  its sockets, and subsequent joins to that room reject with room_over. This
611
692
  finite-close rule does not apply to the endless relay tier.
@@ -618,11 +699,10 @@ described above; its 1 Hz snapshot contains metadata only.
618
699
  terminal generic admission rejection; the public SDK does not expose the
619
700
  WebSocket close code/reason, so do not label every rejected error as this case.
620
701
 
621
- Public quick match is available in the current browser v1 artifact/source and
622
- is queued for npm 1.2.0. npm 1.1.0 consumers must use create/code. Quick match
623
- is unavailable on registered external authorities, which own their capacity,
624
- lobby lifecycle, and any matchmaking. Their reviewed limits can differ from
625
- the Bounty shared-service defaults, and an external authority may be endless.
702
+ Public quick match is unavailable on registered external authorities, which
703
+ own their capacity, lobby lifecycle, and any matchmaking. Their reviewed
704
+ limits can differ from the Bounty shared-service defaults, and an external
705
+ authority may be endless.
626
706
 
627
707
  joinRoom rejection map:
628
708
 
@@ -634,10 +714,9 @@ joinRoom rejection map:
634
714
  - error: malformed host grant, postMessage/socket transport failure, server
635
715
  failure, or the 10-second welcome timeout.
636
716
 
637
- In the current browser/source build and npm 1.2.0+, use error.detail only when
638
- it is a specific documented value such as room_full or room_over. Stable npm
639
- 1.1.0 has no detail field. Generic rejected has multiple causes; do not blind
640
- retry or show “already playing elsewhere” without a distinct detail.
717
+ Use error.detail only when it is a specific documented value such as room_full
718
+ or room_over. Generic rejected has multiple causes; do not blind retry or show
719
+ “already playing elsewhere” without a distinct detail.
641
720
 
642
721
  Multiplayer is curated per game slug. After approval, Bounty Board assigns one
643
722
  of three rails: (a) the built-in casual relay, (b) a bespoke Bounty referee
@@ -677,7 +756,7 @@ https://www.bountyboard.gg/arcade/sdk/external-authoritative-servers.txt
677
756
  - Test initial room.state/players rendering before the first later event.
678
757
  - Test multiplayer reconnect/grace, trySend false, tier-appropriate events/state,
679
758
  and leave.
680
- - Test create/code, and match only when using the current browser build/npm 1.2+.
759
+ - Test create/code, and match on the Bounty shared-service tiers.
681
760
  - For relay, test roomSize default/range, public fan-out (including sender), host
682
761
  succession after grace, byte/rate drops, state.dropped, and absence of end.
683
762
  - For referee/external rooms, test viewer-safe snapshots, authoritative results,
@@ -690,6 +769,13 @@ https://www.bountyboard.gg/arcade/sdk/external-authoritative-servers.txt
690
769
  fire-and-forget and give every promise feature a standalone outcome.
691
770
  - save/load unsupported -> not a Bounty-hosted upload -> use own same-origin store.
692
771
  - save/load unauthenticated -> guest player -> continue with defaults/fallback.
772
+ - SecurityError touching localStorage/sessionStorage/document.cookie -> hosted
773
+ builds run on an opaque origin -> use save()/load(), or storage.install() for
774
+ an engine export that cannot be rewired.
775
+ - Shimmed storage reads empty at boot -> the cloud read had not landed yet ->
776
+ await BBArcade.storage.ready() before restoring progress.
777
+ - storage.mode is 'memory' -> guest, standalone, or off-host -> storage works
778
+ for the session but is never persisted; keep first-run defaults sane.
693
779
  - direct_user_action_required -> show() lost browser activation -> make show()
694
780
  the first line of the click/tap handler.
695
781
  - host_disabled/unavailable -> host or inventory is not ready -> keep the normal
@@ -710,6 +796,5 @@ https://www.bountyboard.gg/arcade/sdk/external-authoritative-servers.txt
710
796
  - Relay room guide: https://www.bountyboard.gg/arcade/sdk/relay-rooms.txt
711
797
  - Standalone types: https://www.bountyboard.gg/arcade-sdk.d.ts
712
798
  - Submit a game: https://www.bountyboard.gg/arcade/submit
713
- - Package README, changelog, AGENTS.md, and design playbook ship in npm 1.1.0.
714
- The external-authority guide is public at the URL above and joins the package
715
- in 1.2.0.
799
+ - Package README, changelog, AGENTS.md, design playbook, relay-room guide, and
800
+ external-authority guide all ship in the npm package.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bountyboard/arcade-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Bounty Board Arcade SDK — leaderboards, cloud saves, rewarded ads, A/B variants, and multiplayer rooms for games on bountyboard.gg",
5
5
  "keywords": [
6
6
  "bountyboard",