@indiegems/gem-web-sdk 7.5.0 → 7.7.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.
@@ -2,7 +2,7 @@
2
2
  "schema_version": 1,
3
3
  "source": {
4
4
  "repo": "IndieGems/gem-web-sdk",
5
- "version": "7.5.0"
5
+ "version": "7.7.0"
6
6
  },
7
7
  "entries": [
8
8
  {
@@ -5836,7 +5836,7 @@
5836
5836
  "url": "https://gemarcade.com/developer/api/game#framestate",
5837
5837
  "signature": "type FrameState = \"waiting\" | \"connected\" | \"refused\" | \"closed\"",
5838
5838
  "documented": true,
5839
- "summary": "Where the bridge to the arcade stands.\n\n`waiting` is the state a game boots in and the one it may sit in forever —\noutside the arcade there is no parent to answer, and that is not an error.\n`connected` is the only state in which the arcade is listening. `refused`\nmeans a page tried to embed this game and its origin was not in\n`allowedEmbedders`; `closed` means a connection existed and ended.\n\nReceived by `onStateChange` and resolved by `awaitBridge`, both of which are\ndocumented — so without a name for this union a game could observe the value\nand not be able to declare a variable holding it.",
5839
+ "summary": "Where the bridge to the arcade stands.\n\n`waiting` is the state a game boots in: framed, and its parent has not\nanswered yet. `connected` is the only state in which the arcade is\nlistening. `refused` is final: the page that framed this game is not one it\naccepts (its origin is not in `allowedEmbedders`), or it speaks no protocol\nversion in common. `closed` means a connection existed and ended.\n\nReceived by `onStateChange` and resolved by `awaitBridge`, both of which are\ndocumented — so without a name for this union a game could observe the value\nand not be able to declare a variable holding it.",
5840
5840
  "deprecated": null,
5841
5841
  "params": [],
5842
5842
  "returns": null,
@@ -5876,7 +5876,7 @@
5876
5876
  "url": "https://gemarcade.com/developer/api/game#gemframe-currentstate",
5877
5877
  "signature": "get currentState(): FrameState",
5878
5878
  "documented": true,
5879
- "summary": "Where the bridge is right now, without waiting.\n\n- `waiting` — the arcade has not connected yet. Normal at startup.\n- `connected` — the bridge is up and everything works.\n- `refused` — the arcade declined the connection.\n- `closed` — the session has ended.\n\nA snapshot, so it can change immediately after you read it. To wait for the\nquestion to be settled once, use [GemFrame.settled](https://gemarcade.com/developer/api/game#gemframe-settled).\n\nA game outside the arcade — opened directly during development — stays\n`waiting` forever, which is the case worth handling: boot anyway, without a\nbridge.",
5879
+ "summary": "Where the bridge is right now, without waiting.\n\n- `waiting` — the arcade has not connected yet. Normal at startup.\n- `connected` — the bridge is up and everything works.\n- `refused` — final: the page that framed the game is not one it accepts,\n or no protocol version is shared.\n- `closed` — the session has ended.\n\nA snapshot, so it can change immediately after you read it. To wait for the\nquestion to be settled once, use [GemFrame.settled](https://gemarcade.com/developer/api/game#gemframe-settled).\n\nA page that frames the game and never answers leaves it `waiting`, which\nis the case worth handling: boot anyway, and say there is no session.",
5880
5880
  "deprecated": null,
5881
5881
  "params": [],
5882
5882
  "returns": null,
@@ -5934,7 +5934,7 @@
5934
5934
  "url": "https://gemarcade.com/developer/api/game#gemframe-settled",
5935
5935
  "signature": "settled(timeoutMs?: number): Promise<FrameState>",
5936
5936
  "documented": true,
5937
- "summary": "Wait until it is known whether there is a bridge, then carry on either way.\n\nResolves as soon as the connection is settled — connected, refused or\nclosed — or when the timeout runs out, with whichever state it is in.\n\nTHE GATE TO AWAIT BEFORE LOADING A HEAVY BUNDLE. The connection offer is\ndelivered to your game rather than polled, so if your main thread is busy\nparsing several megabytes of JavaScript, the offer waits behind that work —\nand by the time it arrives the connection it carried may already be gone.\nAwaiting this first keeps the thread free long enough to accept.\n\nTHE WAIT IS BOUNDED ON PURPOSE, because an embedder is optional: your game\nmust still start when nothing ever answers, just without a bridge. Proceed\non either outcome and read the resolved state to know which kind of boot\nthis is.\n\n```js\nconst state = await gem.frame.settled();\nconst game = await import('./game-engine.js');\ngame.start({ online: state === 'connected' });\n```",
5937
+ "summary": "Wait until it is known whether there is a bridge, then carry on either way.\n\nResolves as soon as the connection is settled — connected, refused or\nclosed — or when the timeout runs out, with whichever state it is in.\n\nTHE GATE TO AWAIT BEFORE LOADING A HEAVY BUNDLE. The connection offer is\ndelivered to your game rather than polled, so if your main thread is busy\nparsing several megabytes of JavaScript, the offer waits behind that work —\nand by the time it arrives the connection it carried may already be gone.\nAwaiting this first keeps the thread free long enough to accept.\n\nTHE WAIT IS BOUNDED ON PURPOSE, because the page framing your game may\nnever answer, and your game must still start and say so. Proceed on either\noutcome and read the resolved state to know which kind of boot this is.\n\n```js\nconst state = await gem.frame.settled();\nconst game = await import('./game-engine.js');\ngame.start({ online: state === 'connected' });\n```",
5938
5938
  "deprecated": null,
5939
5939
  "params": [
5940
5940
  {
@@ -8171,7 +8171,7 @@
8171
8171
  "url": "https://gemarcade.com/developer/api/game#gemviewport-reserved",
8172
8172
  "signature": "readonly reserved: readonly GemReservedArea[]",
8173
8173
  "documented": true,
8174
- "summary": "Rectangles of your frame the arcade's own controls cover — its Back tab in\nthe top-left corner, and the wallets beside it while they are shown (see\n`setWalletVisible`). Keep essential UI and touch targets out of them. Empty\nwhen the arcade covers nothing.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends.",
8174
+ "summary": "Rectangles of your frame the arcade's own controls cover — its Back tab in\nthe top-left corner, and the wallets in the top-right while they are shown (see\n`setWalletVisible`). Keep essential UI and touch targets out of them. Empty\nwhen the arcade covers nothing.\n\nA stylesheet reads the per-corner custom properties the SDK stamps from\nthese instead — `--gem-reserved-tl-height` and friends.",
8175
8175
  "deprecated": null,
8176
8176
  "params": [],
8177
8177
  "returns": null,
@@ -8495,7 +8495,7 @@
8495
8495
  "url": "https://gemarcade.com/developer/api/game#gem-awaitidentity",
8496
8496
  "signature": "awaitIdentity(timeoutMs?: number): Promise<GemIdentity>",
8497
8497
  "documented": true,
8498
- "summary": "The player's identity, waiting for sign-in if it has not finished.\n\nAwait this when you need the ids themselves. Rooms are scoped to your game,\nand your game's id comes from sign-in — but a room call made before it\nlands performs this same wait on your behalf, so awaiting it first is a\nchoice rather than a requirement. In a real session the wait is normally a\nsingle round trip to the page hosting your game.\n\nRejects rather than resolving `null` if the wait runs out: a session that\ncannot sign in cannot do anything else either, and a `null` you might not\ncheck is how a game carries on into a failure it could have reported.",
8498
+ "summary": "The player's identity, waiting for sign-in if it has not finished.\n\nAwait this when you need the ids themselves. Rooms are scoped to your game,\nand your game's id comes from sign-in — but a room call made before it\nlands performs this same wait on your behalf, so awaiting it first is a\nchoice rather than a requirement. In a real session the wait is normally a\nsingle round trip to the page hosting your game.\n\nRejects rather than resolving `null` if the wait runs out: a session that\ncannot sign in cannot do anything else either, and a `null` you might not\ncheck is how a game carries on into a failure it could have reported. It\nrejects at once, without waiting, when the page that framed the game was\nrefused: no token can arrive from it.",
8499
8499
  "deprecated": null,
8500
8500
  "params": [
8501
8501
  {
@@ -8519,7 +8519,7 @@
8519
8519
  "url": "https://gemarcade.com/developer/api/game#gem-awaitbridge",
8520
8520
  "signature": "awaitBridge(timeoutMs?: number): Promise<FrameState | null>",
8521
8521
  "documented": true,
8522
- "summary": "Waits for the connection to the arcade to settle, one way or the other.\n\nResolves with the state it settled into — connected, refused or closed — or\nwith whatever it had reached by `timeoutMs`, which defaults to 10 seconds.\n\nOUTSIDE THE ARCADE IT WAITS THE FULL TIMEOUT and then resolves `waiting`,\nbecause nothing ever answers. It does not return early. That is ten seconds\non every local run, so pass a shorter `timeoutMs` during development if the\nwait gets in your way.\n\n**Await this in your boot entry before importing your main bundle.** The\narcade hands your game its connection as a one-off delivery rather than\nsomething you can ask for later, so if the browser's main thread is busy\nparsing several megabytes of JavaScript when it arrives, your game can miss\nit. Awaiting this keeps the thread free through that window; the moment it\nresolves, load everything.\n\nCarry on whatever it resolves to. `connected` means you are in the arcade;\nanything else means you are running without it, which your game has to\nsurvive anyway — during local development, that is the normal case.",
8522
+ "summary": "Waits for the connection to the arcade to settle, one way or the other.\n\nResolves with the state it settled into — connected, refused or closed — or\nwith whatever it had reached by `timeoutMs`, which defaults to 10 seconds.\n\nA game is always framed (`createGem` throws [GemUnframedError](https://gemarcade.com/developer/api/game#gemunframederror)\notherwise), so this normally resolves `connected` within a moment. It\nresolves `refused` at once when the page that framed the game is not one it\naccepts, and `waiting` only when the page framing it never answers.\n\n**Await this in your boot entry before importing your main bundle.** The\narcade hands your game its connection as a one-off delivery rather than\nsomething you can ask for later, so if the browser's main thread is busy\nparsing several megabytes of JavaScript when it arrives, your game can miss\nit. Awaiting this keeps the thread free through that window; the moment it\nresolves, load everything.\n\nCarry on whatever it resolves to. `connected` means a Gem host is\nlistening — the Arcade, the Editor or the dev host on your machine;\nanything else means no session, which your game has to survive with a\nmessage rather than a hang.",
8523
8523
  "deprecated": null,
8524
8524
  "params": [
8525
8525
  {
@@ -8543,7 +8543,7 @@
8543
8543
  "url": "https://gemarcade.com/developer/api/game#gem-player",
8544
8544
  "signature": "readonly player: GemPlayer | null",
8545
8545
  "documented": true,
8546
- "summary": "The player's display identity — the name, handle and avatar you show them.\n\n`null` until the arcade sends it, and always `null` outside the arcade.\nNothing obliges it to be sent at all, so treat its absence as normal and\nhave a fallback: a game with no display name is fine, a game that waits\nforever for one is not.\n\nThe name and handle are player-authored, so they arrive wrapped rather than\nas plain strings — assigning one to `innerHTML` will not compile. Unwrap at\nthe point you render.\n\nThis is distinct from [Gem.identity](https://gemarcade.com/developer/api/game#gem-identity), which is who the platform says\nthey are, and from [Gem.me](https://gemarcade.com/developer/api/game#gem-me), which is their data in your game.",
8546
+ "summary": "The player's display identity — the name, handle and avatar you show them.\n\n`null` until the arcade sends it. Nothing obliges it to be sent at all, so treat its absence as normal and\nhave a fallback: a game with no display name is fine, a game that waits\nforever for one is not.\n\nThe name and handle are player-authored, so they arrive wrapped rather than\nas plain strings — assigning one to `innerHTML` will not compile. Unwrap at\nthe point you render.\n\nThis is distinct from [Gem.identity](https://gemarcade.com/developer/api/game#gem-identity), which is who the platform says\nthey are, and from [Gem.me](https://gemarcade.com/developer/api/game#gem-me), which is their data in your game.",
8547
8547
  "deprecated": null,
8548
8548
  "params": [],
8549
8549
  "returns": null,
@@ -8960,7 +8960,7 @@
8960
8960
  "url": "https://gemarcade.com/developer/api/game#gem-awaitcohorts",
8961
8961
  "signature": "awaitCohorts(timeoutMs?: number): Promise<GemCohorts | null>",
8962
8962
  "documented": true,
8963
- "summary": "The experiment cohorts, waiting up to `timeoutMs` for them.\n\nResolves `null` rather than rejecting — outside the arcade too, once the\nwait is out, because no parent is there to answer. Use it at the ONE point\nyour game branches on a cohort — typically\nthe first run's opening — and give it a short budget: the answer normally\narrives within the first second, and a player kept on a splash screen for a\nvalue that changes a tutorial's wording has been made to pay for an\nexperiment they cannot see.",
8963
+ "summary": "The experiment cohorts, waiting up to `timeoutMs` for them.\n\nResolves `null` rather than rejecting once the wait is out, or at once if\nthe page that framed the game was refused. Use it at the ONE point\nyour game branches on a cohort — typically\nthe first run's opening — and give it a short budget: the answer normally\narrives within the first second, and a player kept on a splash screen for a\nvalue that changes a tutorial's wording has been made to pay for an\nexperiment they cannot see.",
8964
8964
  "deprecated": null,
8965
8965
  "params": [
8966
8966
  {
@@ -9032,7 +9032,7 @@
9032
9032
  "url": "https://gemarcade.com/developer/api/game#gem-setwalletvisible",
9033
9033
  "signature": "setWalletVisible(visible: boolean, options?: WalletVisibleOptions): boolean",
9034
9034
  "documented": true,
9035
- "summary": "Show or hide the player's wallets — Gems, Tickets, the achievement score —\nthat the arcade draws over your game. **They are shown until you say\notherwise**: hide them during gameplay, show them again on your menus.\n\n```js\nlevel.onStart = () => gem.setWalletVisible(false);\nmenu.onOpen = () => gem.setWalletVisible(true);\n// A screen that sells in one currency may narrow it:\nticketStore.onOpen = () => gem.setWalletVisible(true, { only: ['ARCADE_TICKETS'] });\n```\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last. While hidden, the arcade still pops a wallet up for a\nmoment when something happens to it — a reward you cued, a spend, a\nlanding — and your state never cancels that. Shown wallets cover part of\nyour frame beside the Back tab, which arrives as a rectangle in\n`gem.viewport.reserved`; hiding them gives that space back. A wallet the\narcade pops up for a moment floats over your game and moves nothing.\n\n**Never draw a platform balance yourself**, and never show a pending state\nfor one: the arcade takes every figure from the platform. If this returns\n`false`, do nothing in its place.",
9035
+ "summary": "Show or hide the player's wallets — Gems, Tickets, the achievement score —\nthat the arcade draws over your game. **They are shown until you say\notherwise**: hide them during gameplay, show them again on your menus.\n\n```js\nlevel.onStart = () => gem.setWalletVisible(false);\nmenu.onOpen = () => gem.setWalletVisible(true);\n// A screen that sells in one currency may narrow it:\nticketStore.onOpen = () => gem.setWalletVisible(true, { only: ['ARCADE_TICKETS'] });\n```\n\n**It is screen state, not a toggle.** Each call is the whole answer and\nreplaces the last. While hidden, the arcade still pops a wallet up for a\nmoment when something happens to it — a reward you cued, a spend, a\nlanding — and your state never cancels that. Shown wallets cover part of\nyour frame's top-right corner, which arrives as a rectangle in\n`gem.viewport.reserved`; hiding them gives that space back. A wallet the\narcade pops up for a moment floats over your game and moves nothing.\n\n**Never draw a platform balance yourself**, and never show a pending state\nfor one: the arcade takes every figure from the platform. If this returns\n`false`, do nothing in its place.",
9036
9036
  "deprecated": null,
9037
9037
  "params": [
9038
9038
  {
@@ -9103,7 +9103,7 @@
9103
9103
  "url": "https://gemarcade.com/developer/api/game#gem-frame",
9104
9104
  "signature": "readonly frame: GemFrame",
9105
9105
  "documented": true,
9106
- "summary": "Your game's connection to the arcade.\n\nALWAYS PRESENT, even when nothing is on the other end. Running outside the\narcade does not make this `null` — the frame simply stays in `waiting`\nforever, which is the state to read when you want to know whether anyone\never answered. Do not write a `=== null` branch; it can never run.\n\n**This is where the loading contract lives.** Call `progress()` as your\ngame loads and `ready()` when it is playable, so the arcade can show a real\nloading state instead of a blank frame. Both are on the platform's release\nchecklist.",
9106
+ "summary": "Your game's connection to the arcade.\n\nALWAYS PRESENT, even when nothing is on the other end yet: a page that\nnever answers leaves the frame in `waiting`, and a refused one ends it in\n`refused`, which is the state to read when you want to know whether anyone\never answered. Do not write a `=== null` branch; it can never run.\n\n**This is where the loading contract lives.** Call `progress()` as your\ngame loads and `ready()` when it is playable, so the arcade can show a real\nloading state instead of a blank frame. Both are on the platform's release\nchecklist.",
9107
9107
  "deprecated": null,
9108
9108
  "params": [],
9109
9109
  "returns": null,
@@ -9241,7 +9241,7 @@
9241
9241
  "url": "https://gemarcade.com/developer/api/game#creategem",
9242
9242
  "signature": "function createGem(options?: GemOptions): Gem",
9243
9243
  "documented": true,
9244
- "summary": "Connect your game to the Gem Arcade. This is the one call every game makes.\n\nIt assembles everything and connects to the arcade that framed your game. The\nsame code runs on your machine and in production without a flag — but it needs\na parent either way: run it framed, and until one answers the SDK says so\nrather than letting you assume a session you do not have.\n\nOne dependency, imported like anything else. The install is at\nhttps://gemarcade.com/developer/docs/getting-started#3-add-the-sdk\n\n```js\nimport { createGem } from '@indiegems/gem-web-sdk/sdk';\n\nconst gem = createGem();\n\nawait gem.awaitBridge(); // before loading your main bundle\nconst me = await gem.awaitIdentity(); // who is playing, once sign-in completes\n```\n\nNo options are needed. The Gem Arcade's website and app are always allowed to\nembed your game ([ARCADE_EMBEDDERS](https://gemarcade.com/developer/api/game#arcade_embedders)), and the arcade tells your game\nwhich build it is.\n\nA dev host on your own machine (`gemctl serve`, the Gem Editor) needs no\nentry either: it frames a locally-served game from a loopback origin whose\nport changes every run, and the SDK admits it by rule — only while the game\nitself is served from loopback, so nothing changes once you publish.\n\nPass `allowedEmbedders` only when a site of your own frames your game. It\nadds to the arcade's origins; see [GemOptions.allowedEmbedders](https://gemarcade.com/developer/api/game#gemoptions-allowedembedders).",
9244
+ "summary": "Connect your game to the Gem Arcade. This is the one call every game makes.\n\nIt assembles everything and connects to the arcade that framed your game. The\nsame code runs on your machine and in production without a flag — but it needs\na frame either way. Opened at the top level it throws\n[GemUnframedError](https://gemarcade.com/developer/api/game#gemunframederror), which says how to run it: in the Arcade, the Editor,\nor the Gem dev host on your machine (`npm run dev`, signed in to `gemctl`).\n\nOne dependency, imported like anything else. The install is at\nhttps://gemarcade.com/developer/docs/getting-started#3-add-the-sdk\n\n```js\nimport { createGem } from '@indiegems/gem-web-sdk/sdk';\n\nconst gem = createGem();\n\nawait gem.awaitBridge(); // before loading your main bundle\nconst me = await gem.awaitIdentity(); // who is playing, once sign-in completes\n```\n\nNo options are needed. The Gem Arcade's website and app are always allowed to\nembed your game ([ARCADE_EMBEDDERS](https://gemarcade.com/developer/api/game#arcade_embedders)), and the arcade tells your game\nwhich build it is.\n\nA dev host on your own machine (`gemctl serve`, the Gem Editor) needs no\nentry either: it frames a locally-served game from a loopback origin whose\nport changes every run, and the SDK admits it by rule — only while the game\nitself is served from loopback, so nothing changes once you publish.\n\nPass `allowedEmbedders` only when a site of your own frames your game. It\nadds to the arcade's origins; see [GemOptions.allowedEmbedders](https://gemarcade.com/developer/api/game#gemoptions-allowedembedders).",
9245
9245
  "deprecated": null,
9246
9246
  "params": [
9247
9247
  {
@@ -9399,6 +9399,40 @@
9399
9399
  "defaultValue": null,
9400
9400
  "guides": []
9401
9401
  },
9402
+ {
9403
+ "id": "LeaderboardPeriod",
9404
+ "name": "LeaderboardPeriod",
9405
+ "anchor": "leaderboardperiod",
9406
+ "group": "leaderboards",
9407
+ "url": "https://gemarcade.com/developer/api/game#leaderboardperiod",
9408
+ "signature": "interface LeaderboardPeriod",
9409
+ "documented": true,
9410
+ "summary": "Which period a `per_period` board's result is for.\n\nA board whose config sets `per_period: true` exists once per period of the\nchallenge its run type names: one board for each day of a daily challenge,\neach week of a weekly one. Present on a result exactly when the board is one\nof those; absent — not `null` — on an ordinary board.\n\n⚠️ **Players on the same date share a board, whatever their time zone.** On\na challenge that follows each player's own time zone (the default), a date\nbegins for each player at their own midnight, so one day's board is open for\nabout 50 hours worldwide and the standings can still change after YOUR day\nhas ended. [LeaderboardPeriod.final](https://gemarcade.com/developer/api/game#leaderboardperiod-final) says when they no longer can.",
9411
+ "deprecated": null,
9412
+ "params": [],
9413
+ "returns": null,
9414
+ "throws": null,
9415
+ "example": null,
9416
+ "defaultValue": null,
9417
+ "guides": []
9418
+ },
9419
+ {
9420
+ "id": "PeriodOptions",
9421
+ "name": "PeriodOptions",
9422
+ "anchor": "periodoptions",
9423
+ "group": "leaderboards",
9424
+ "url": "https://gemarcade.com/developer/api/game#periodoptions",
9425
+ "signature": "interface PeriodOptions",
9426
+ "documented": true,
9427
+ "summary": "Which period of a `per_period` board to read. Shared by every read.\n\nName at most one of [PeriodOptions.periodsBack](https://gemarcade.com/developer/api/game#periodoptions-periodsback) and\n[PeriodOptions.period](https://gemarcade.com/developer/api/game#periodoptions-period); with neither, the read answers for the current\nperiod. On a board that is not `per_period` both are refused with `invalid`\nrather than ignored — asking an all-time board for yesterday and being\nhanded all time would be a different answer with nothing saying so.",
9428
+ "deprecated": null,
9429
+ "params": [],
9430
+ "returns": null,
9431
+ "throws": null,
9432
+ "example": null,
9433
+ "defaultValue": null,
9434
+ "guides": []
9435
+ },
9402
9436
  {
9403
9437
  "id": "ShowcaseApi",
9404
9438
  "name": "ShowcaseApi",
@@ -12980,6 +13014,142 @@
12980
13014
  "defaultValue": null,
12981
13015
  "guides": []
12982
13016
  },
13017
+ {
13018
+ "id": "GemUnframedError",
13019
+ "name": "GemUnframedError",
13020
+ "anchor": "gemunframederror",
13021
+ "group": "failures",
13022
+ "url": "https://gemarcade.com/developer/api/game#gemunframederror",
13023
+ "signature": "class GemUnframedError",
13024
+ "documented": true,
13025
+ "summary": "Your game was opened without a Gem frame around it.\n\nThrown by `createGem` when the page is the top-level document: nothing\nframed it, so nothing can hand it a player, saves, config or rooms. The SDK\nneeds that frame — the Gem Arcade, the Gem Editor, or the Gem dev host on\nyour own machine — and refuses to start without one rather than running a\ngame that silently has no player.\n\nYou do not construct this. Run the game framed instead: `npm run dev`\n(`gemctl serve start . --dev`) while you edit, `gemctl serve start .` for a\nbuild, signed in to `gemctl` either way. Never open the address the dev\nserver itself prints.\n\nA unit test that constructs the SDK in a simulated DOM has to give its\nwindow a parent of its own, because a simulated top-level window is exactly\nwhat this refuses.",
13026
+ "deprecated": null,
13027
+ "params": [],
13028
+ "returns": null,
13029
+ "throws": null,
13030
+ "example": null,
13031
+ "defaultValue": null,
13032
+ "guides": []
13033
+ },
13034
+ {
13035
+ "id": "LeaderboardPeriod.id",
13036
+ "name": "id",
13037
+ "anchor": "leaderboardperiod-id",
13038
+ "group": "leaderboards",
13039
+ "url": "https://gemarcade.com/developer/api/game#leaderboardperiod-id",
13040
+ "signature": "readonly id: string",
13041
+ "documented": true,
13042
+ "summary": "The period this board is for: the date for a daily challenge, the Monday\nfor a weekly, the 1st for a monthly (`2026-10-02`), or a cron challenge's\nstart as `2026-10-02T06:00:00Z`.\n\n**This is the board's identity.** Pass it back as\n[GetLeaderboardOptions.period](https://gemarcade.com/developer/api/game#getleaderboardoptions-period) to read this same board again later —\nfrom a shared link, or a history screen — and key any cache on it.",
13043
+ "deprecated": null,
13044
+ "params": [],
13045
+ "returns": null,
13046
+ "throws": null,
13047
+ "example": null,
13048
+ "defaultValue": null,
13049
+ "guides": []
13050
+ },
13051
+ {
13052
+ "id": "LeaderboardPeriod.localDate",
13053
+ "name": "localDate",
13054
+ "anchor": "leaderboardperiod-localdate",
13055
+ "group": "leaderboards",
13056
+ "url": "https://gemarcade.com/developer/api/game#leaderboardperiod-localdate",
13057
+ "signature": "readonly localDate?: string",
13058
+ "documented": true,
13059
+ "summary": "The period's calendar date, `YYYY-MM-DD` — the same value\n`gem.challenges.list()` gives as `periodLocalDate` for the challenge, so\nthe two can be compared. Absent for a cron challenge, which has no date.",
13060
+ "deprecated": null,
13061
+ "params": [],
13062
+ "returns": null,
13063
+ "throws": null,
13064
+ "example": null,
13065
+ "defaultValue": null,
13066
+ "guides": []
13067
+ },
13068
+ {
13069
+ "id": "LeaderboardPeriod.startAt",
13070
+ "name": "startAt",
13071
+ "anchor": "leaderboardperiod-startat",
13072
+ "group": "leaderboards",
13073
+ "url": "https://gemarcade.com/developer/api/game#leaderboardperiod-startat",
13074
+ "signature": "readonly startAt?: number",
13075
+ "documented": true,
13076
+ "summary": "When this period began for the player the read answered for, in Unix\nseconds. Absent when the read answered for no player — a server or a\nfunction that named none — on a challenge that follows each player's own\ntime zone.",
13077
+ "deprecated": null,
13078
+ "params": [],
13079
+ "returns": null,
13080
+ "throws": null,
13081
+ "example": null,
13082
+ "defaultValue": null,
13083
+ "guides": []
13084
+ },
13085
+ {
13086
+ "id": "LeaderboardPeriod.endsAt",
13087
+ "name": "endsAt",
13088
+ "anchor": "leaderboardperiod-endsat",
13089
+ "group": "leaderboards",
13090
+ "url": "https://gemarcade.com/developer/api/game#leaderboardperiod-endsat",
13091
+ "signature": "readonly endsAt?: number",
13092
+ "documented": true,
13093
+ "summary": "When this period ends for that player, in Unix seconds: their own runs stop\ncounting towards this board then.\n\n⚠️ **Not when the board stops changing.** Players in later time zones are\nstill on this date after it; see [LeaderboardPeriod.closesAt](https://gemarcade.com/developer/api/game#leaderboardperiod-closesat) and\n[LeaderboardPeriod.final](https://gemarcade.com/developer/api/game#leaderboardperiod-final). Absent exactly when\n[LeaderboardPeriod.startAt](https://gemarcade.com/developer/api/game#leaderboardperiod-startat) is.",
13094
+ "deprecated": null,
13095
+ "params": [],
13096
+ "returns": null,
13097
+ "throws": null,
13098
+ "example": null,
13099
+ "defaultValue": null,
13100
+ "guides": []
13101
+ },
13102
+ {
13103
+ "id": "LeaderboardPeriod.opensAt",
13104
+ "name": "opensAt",
13105
+ "anchor": "leaderboardperiod-opensat",
13106
+ "group": "leaderboards",
13107
+ "url": "https://gemarcade.com/developer/api/game#leaderboardperiod-opensat",
13108
+ "signature": "readonly opensAt: number",
13109
+ "documented": true,
13110
+ "summary": "When this board started accepting runs from anyone, in Unix seconds. `0`\nwhen the platform sent something this SDK could not read.",
13111
+ "deprecated": null,
13112
+ "params": [],
13113
+ "returns": null,
13114
+ "throws": null,
13115
+ "example": null,
13116
+ "defaultValue": null,
13117
+ "guides": []
13118
+ },
13119
+ {
13120
+ "id": "LeaderboardPeriod.closesAt",
13121
+ "name": "closesAt",
13122
+ "anchor": "leaderboardperiod-closesat",
13123
+ "group": "leaderboards",
13124
+ "url": "https://gemarcade.com/developer/api/game#leaderboardperiod-closesat",
13125
+ "signature": "readonly closesAt: number",
13126
+ "documented": true,
13127
+ "summary": "When this board stops accepting runs from anyone, in Unix seconds. `0`\nwhen the platform sent something this SDK could not read.",
13128
+ "deprecated": null,
13129
+ "params": [],
13130
+ "returns": null,
13131
+ "throws": null,
13132
+ "example": null,
13133
+ "defaultValue": null,
13134
+ "guides": []
13135
+ },
13136
+ {
13137
+ "id": "LeaderboardPeriod.final",
13138
+ "name": "final",
13139
+ "anchor": "leaderboardperiod-final",
13140
+ "group": "leaderboards",
13141
+ "url": "https://gemarcade.com/developer/api/game#leaderboardperiod-final",
13142
+ "signature": "readonly final: boolean",
13143
+ "documented": true,
13144
+ "summary": "`true` once no run can still arrive, so the standings will not change\nagain — stop polling, and say \"final\" if your screen says anything.\n\nWhile it is `false` the standings may change, **including after the\nviewer's own period has ended**: a \"you finished 3rd today\" shown at the\nplayer's midnight can be wrong an hour later. Say \"so far\" until this is\n`true`.",
13145
+ "deprecated": null,
13146
+ "params": [],
13147
+ "returns": null,
13148
+ "throws": null,
13149
+ "example": null,
13150
+ "defaultValue": null,
13151
+ "guides": []
13152
+ },
12983
13153
  {
12984
13154
  "id": "LeaderboardEntry.playerId",
12985
13155
  "name": "playerId",
@@ -13124,7 +13294,7 @@
13124
13294
  "url": "https://gemarcade.com/developer/api/game#leaderboardentry-occurredat",
13125
13295
  "signature": "readonly occurredAt: number",
13126
13296
  "documented": true,
13127
- "summary": "When the ranked run occurred — Unix seconds.",
13297
+ "summary": "When the ranked run occurred — Unix seconds.\n\n⚠️ **`0` on a `per_period` board whose challenge follows each player's own\ntime zone** (the default `local` boundary). The platform serves no time\nthere: beside the board's date, the time of a run would show which time\nzones its player could be in. Render no time for `0` rather than 1970.\nRanks are unaffected — ties are still broken by the real time.",
13128
13298
  "deprecated": null,
13129
13299
  "params": [],
13130
13300
  "returns": null,
@@ -13337,6 +13507,23 @@
13337
13507
  "defaultValue": null,
13338
13508
  "guides": []
13339
13509
  },
13510
+ {
13511
+ "id": "Leaderboard.period",
13512
+ "name": "period",
13513
+ "anchor": "leaderboard-period",
13514
+ "group": "leaderboards",
13515
+ "url": "https://gemarcade.com/developer/api/game#leaderboard-period",
13516
+ "signature": "readonly period?: LeaderboardPeriod",
13517
+ "documented": true,
13518
+ "summary": "Which period this result is for, on a board whose config sets\n`per_period`. Absent on an ordinary board, which ranks every run.\n\nOnce `period.final` is `true` the board no longer changes and need not be\npolled.",
13519
+ "deprecated": null,
13520
+ "params": [],
13521
+ "returns": null,
13522
+ "throws": null,
13523
+ "example": null,
13524
+ "defaultValue": null,
13525
+ "guides": []
13526
+ },
13340
13527
  {
13341
13528
  "id": "RankedRow.rank",
13342
13529
  "name": "rank",
@@ -13362,7 +13549,7 @@
13362
13549
  "url": "https://gemarcade.com/developer/api/game#rankedrow-occurredat",
13363
13550
  "signature": "readonly occurredAt: number",
13364
13551
  "documented": true,
13365
- "summary": "When the ranked run occurred — Unix seconds.",
13552
+ "summary": "When the ranked run occurred — Unix seconds. `0` on a `per_period` board\nwhose challenge follows each player's own time zone, where the platform\nserves no time; render nothing for it.",
13366
13553
  "deprecated": null,
13367
13554
  "params": [],
13368
13555
  "returns": null,
@@ -13413,7 +13600,7 @@
13413
13600
  "url": "https://gemarcade.com/developer/api/game#entityleaderboardentry-occurredat",
13414
13601
  "signature": "readonly occurredAt: number",
13415
13602
  "documented": true,
13416
- "summary": "When the ranked run occurred — Unix seconds.",
13603
+ "summary": "When the ranked run occurred — Unix seconds. `0` on a `per_period` board\nwhose challenge follows each player's own time zone, where the platform\nserves no time; render nothing for it.",
13417
13604
  "deprecated": null,
13418
13605
  "params": [],
13419
13606
  "returns": null,
@@ -13660,6 +13847,125 @@
13660
13847
  "defaultValue": null,
13661
13848
  "guides": []
13662
13849
  },
13850
+ {
13851
+ "id": "EntityLeaderboard.period",
13852
+ "name": "period",
13853
+ "anchor": "entityleaderboard-period",
13854
+ "group": "leaderboards",
13855
+ "url": "https://gemarcade.com/developer/api/game#entityleaderboard-period",
13856
+ "signature": "readonly period?: LeaderboardPeriod",
13857
+ "documented": true,
13858
+ "summary": "Which period these runs are from, on a board whose config sets\n`per_period`: `entries`, `personalBest` and `totalRuns`\nare then that ONE period's, not the entity's whole history. Absent on an\nordinary board.",
13859
+ "deprecated": null,
13860
+ "params": [],
13861
+ "returns": null,
13862
+ "throws": null,
13863
+ "example": null,
13864
+ "defaultValue": null,
13865
+ "guides": []
13866
+ },
13867
+ {
13868
+ "id": "PeriodOptions.periodsBack",
13869
+ "name": "periodsBack",
13870
+ "anchor": "periodoptions-periodsback",
13871
+ "group": "leaderboards",
13872
+ "url": "https://gemarcade.com/developer/api/game#periodoptions-periodsback",
13873
+ "signature": "readonly periodsBack?: number",
13874
+ "documented": true,
13875
+ "summary": "How many periods back to read. `0` — the default — is the current period;\n`1` is yesterday's board for a daily challenge, last week's for a weekly.\n\nAn integer from 0 to 400. Anything else is a `TypeError` before the\nrequest is made.",
13876
+ "deprecated": null,
13877
+ "params": [],
13878
+ "returns": null,
13879
+ "throws": null,
13880
+ "example": null,
13881
+ "defaultValue": null,
13882
+ "guides": []
13883
+ },
13884
+ {
13885
+ "id": "PeriodOptions.period",
13886
+ "name": "period",
13887
+ "anchor": "periodoptions-period",
13888
+ "group": "leaderboards",
13889
+ "url": "https://gemarcade.com/developer/api/game#periodoptions-period",
13890
+ "signature": "readonly period?: string",
13891
+ "documented": true,
13892
+ "summary": "The period to read, as an earlier result's [LeaderboardPeriod.id](https://gemarcade.com/developer/api/game#leaderboardperiod-id) —\nfor a board reached from a shared link or a history screen. At most 400\nperiods back.\n\n⚠️ A period that has not begun for the player is refused, even when it has\nalready begun for players in time zones ahead of theirs.",
13893
+ "deprecated": null,
13894
+ "params": [],
13895
+ "returns": null,
13896
+ "throws": null,
13897
+ "example": null,
13898
+ "defaultValue": null,
13899
+ "guides": []
13900
+ },
13901
+ {
13902
+ "id": "PeriodOptions.playerId",
13903
+ "name": "playerId",
13904
+ "anchor": "periodoptions-playerid",
13905
+ "group": "leaderboards",
13906
+ "url": "https://gemarcade.com/developer/api/game#periodoptions-playerid",
13907
+ "signature": "readonly playerId?: string",
13908
+ "documented": true,
13909
+ "summary": "For a serverless function or a dedicated server: the player whose period\nto answer for. Their credential names no player, so without this a\n`per_period` board on each player's own time zone is answered in UTC,\nwhich is not that player's day.\n\nIgnored in the browser, where the read is always for the signed-in player.",
13910
+ "deprecated": null,
13911
+ "params": [],
13912
+ "returns": null,
13913
+ "throws": null,
13914
+ "example": null,
13915
+ "defaultValue": null,
13916
+ "guides": []
13917
+ },
13918
+ {
13919
+ "id": "EntityLeaderboardOptions.periodsBack",
13920
+ "name": "periodsBack",
13921
+ "anchor": "entityleaderboardoptions-periodsback",
13922
+ "group": "leaderboards",
13923
+ "url": "https://gemarcade.com/developer/api/game#entityleaderboardoptions-periodsback",
13924
+ "signature": "readonly periodsBack?: number",
13925
+ "documented": true,
13926
+ "summary": "How many periods back to read. `0` — the default — is the current period;\n`1` is yesterday's board for a daily challenge, last week's for a weekly.\n\nAn integer from 0 to 400. Anything else is a `TypeError` before the\nrequest is made.",
13927
+ "deprecated": null,
13928
+ "params": [],
13929
+ "returns": null,
13930
+ "throws": null,
13931
+ "example": null,
13932
+ "defaultValue": null,
13933
+ "guides": []
13934
+ },
13935
+ {
13936
+ "id": "EntityLeaderboardOptions.period",
13937
+ "name": "period",
13938
+ "anchor": "entityleaderboardoptions-period",
13939
+ "group": "leaderboards",
13940
+ "url": "https://gemarcade.com/developer/api/game#entityleaderboardoptions-period",
13941
+ "signature": "readonly period?: string",
13942
+ "documented": true,
13943
+ "summary": "The period to read, as an earlier result's [LeaderboardPeriod.id](https://gemarcade.com/developer/api/game#leaderboardperiod-id) —\nfor a board reached from a shared link or a history screen. At most 400\nperiods back.\n\n⚠️ A period that has not begun for the player is refused, even when it has\nalready begun for players in time zones ahead of theirs.",
13944
+ "deprecated": null,
13945
+ "params": [],
13946
+ "returns": null,
13947
+ "throws": null,
13948
+ "example": null,
13949
+ "defaultValue": null,
13950
+ "guides": []
13951
+ },
13952
+ {
13953
+ "id": "EntityLeaderboardOptions.playerId",
13954
+ "name": "playerId",
13955
+ "anchor": "entityleaderboardoptions-playerid",
13956
+ "group": "leaderboards",
13957
+ "url": "https://gemarcade.com/developer/api/game#entityleaderboardoptions-playerid",
13958
+ "signature": "readonly playerId?: string",
13959
+ "documented": true,
13960
+ "summary": "For a serverless function or a dedicated server: the player whose period\nto answer for. Their credential names no player, so without this a\n`per_period` board on each player's own time zone is answered in UTC,\nwhich is not that player's day.\n\nIgnored in the browser, where the read is always for the signed-in player.",
13961
+ "deprecated": null,
13962
+ "params": [],
13963
+ "returns": null,
13964
+ "throws": null,
13965
+ "example": null,
13966
+ "defaultValue": null,
13967
+ "guides": []
13968
+ },
13663
13969
  {
13664
13970
  "id": "EntityLeaderboardOptions.bucket1",
13665
13971
  "name": "bucket1",
@@ -13728,6 +14034,57 @@
13728
14034
  "defaultValue": null,
13729
14035
  "guides": []
13730
14036
  },
14037
+ {
14038
+ "id": "GetLeaderboardOptions.periodsBack",
14039
+ "name": "periodsBack",
14040
+ "anchor": "getleaderboardoptions-periodsback",
14041
+ "group": "leaderboards",
14042
+ "url": "https://gemarcade.com/developer/api/game#getleaderboardoptions-periodsback",
14043
+ "signature": "readonly periodsBack?: number",
14044
+ "documented": true,
14045
+ "summary": "How many periods back to read. `0` — the default — is the current period;\n`1` is yesterday's board for a daily challenge, last week's for a weekly.\n\nAn integer from 0 to 400. Anything else is a `TypeError` before the\nrequest is made.",
14046
+ "deprecated": null,
14047
+ "params": [],
14048
+ "returns": null,
14049
+ "throws": null,
14050
+ "example": null,
14051
+ "defaultValue": null,
14052
+ "guides": []
14053
+ },
14054
+ {
14055
+ "id": "GetLeaderboardOptions.period",
14056
+ "name": "period",
14057
+ "anchor": "getleaderboardoptions-period",
14058
+ "group": "leaderboards",
14059
+ "url": "https://gemarcade.com/developer/api/game#getleaderboardoptions-period",
14060
+ "signature": "readonly period?: string",
14061
+ "documented": true,
14062
+ "summary": "The period to read, as an earlier result's [LeaderboardPeriod.id](https://gemarcade.com/developer/api/game#leaderboardperiod-id) —\nfor a board reached from a shared link or a history screen. At most 400\nperiods back.\n\n⚠️ A period that has not begun for the player is refused, even when it has\nalready begun for players in time zones ahead of theirs.",
14063
+ "deprecated": null,
14064
+ "params": [],
14065
+ "returns": null,
14066
+ "throws": null,
14067
+ "example": null,
14068
+ "defaultValue": null,
14069
+ "guides": []
14070
+ },
14071
+ {
14072
+ "id": "GetLeaderboardOptions.playerId",
14073
+ "name": "playerId",
14074
+ "anchor": "getleaderboardoptions-playerid",
14075
+ "group": "leaderboards",
14076
+ "url": "https://gemarcade.com/developer/api/game#getleaderboardoptions-playerid",
14077
+ "signature": "readonly playerId?: string",
14078
+ "documented": true,
14079
+ "summary": "For a serverless function or a dedicated server: the player whose period\nto answer for. Their credential names no player, so without this a\n`per_period` board on each player's own time zone is answered in UTC,\nwhich is not that player's day.\n\nIgnored in the browser, where the read is always for the signed-in player.",
14080
+ "deprecated": null,
14081
+ "params": [],
14082
+ "returns": null,
14083
+ "throws": null,
14084
+ "example": null,
14085
+ "defaultValue": null,
14086
+ "guides": []
14087
+ },
13731
14088
  {
13732
14089
  "id": "GetLeaderboardOptions.bucket1",
13733
14090
  "name": "bucket1",
@@ -13787,7 +14144,7 @@
13787
14144
  "url": "https://gemarcade.com/developer/api/game#leaderboardsapi-get",
13788
14145
  "signature": "get(leaderboardType: string, options?: GetLeaderboardOptions): Promise<Leaderboard>",
13789
14146
  "documented": true,
13790
- "summary": "Read one board, whole.\n\nA bucketed board (config `bucket_by`) needs exactly its bucket values —\n`get('weekly', { bucket1: 'na' })` — and refuses a mismatch. Poll no\nfaster than the result's `updateFrequencySeconds`; the server regenerates\nat that cadence and faster polling re-reads the same rendering.",
14147
+ "summary": "Read one board, whole.\n\nA bucketed board (config `bucket_by`) needs exactly its bucket values —\n`get('weekly', { bucket1: 'na' })` — and refuses a mismatch. Poll no\nfaster than the result's `updateFrequencySeconds`; the server regenerates\nat that cadence and faster polling re-reads the same rendering.\n\nA `per_period` board answers for one period — the current one by default:\n\n```js\nconst today = await gem.leaderboards.get('daily');\nconst yesterday = await gem.leaderboards.get('daily', { periodsBack: 1 });\nlabel(today.period.final ? 'Final standings' : 'Standings so far');\n```",
13791
14148
  "deprecated": null,
13792
14149
  "params": [
13793
14150
  {
@@ -13804,7 +14161,7 @@
13804
14161
  }
13805
14162
  ],
13806
14163
  "returns": null,
13807
- "throws": "`not_found` when no board with this id is in the channel's active\n config, `invalid` when bucket values do not match the board's\n `bucket_by`.",
14164
+ "throws": "`TypeError` when `periodsBack` is not an integer from 0 to 400,\n `period` is not a period id, or both are passed — refused before the\n request.\n\n`not_found` when no board with this id is in the channel's active\n config, `invalid` when bucket values do not match the board's\n `bucket_by`, when a period is asked of a board that is not `per_period`\n (`code` `invalid_period`), or when the period has not begun for the\n player (`code` `period_not_begun`).",
13808
14165
  "example": null,
13809
14166
  "defaultValue": null,
13810
14167
  "guides": []
@@ -13817,7 +14174,7 @@
13817
14174
  "url": "https://gemarcade.com/developer/api/game#leaderboardsapi-friends",
13818
14175
  "signature": "friends(leaderboardType: string, options?: GetLeaderboardOptions): Promise<Leaderboard>",
13819
14176
  "documented": true,
13820
- "summary": "The same board, ranked over you and your accepted friends only.\n\nSame run type, ordering, buckets and `maxSize` as `get`, and the\nsame cached cadence — but ranks are positions **within this restricted\nboard**, not global ones. Your own rows carry\n[LeaderboardEntry.isViewer](https://gemarcade.com/developer/api/game#leaderboardentry-isviewer), which is what lets a standings screen\nmark \"you\"; on `get` that field is absent rather than false, because\na global board does not answer the question at all.\n\nA player with no friends gets a board of one — their own rows — not an\nerror. Character-owned runs are never on a friend board: friendship is a\nplayer-to-player relation.\n\n```js\nconst board = await gem.leaderboards.friends('time-trial');\nfor (const row of board.entries) {\n const name = row.displayTag ? text.toPlain(row.displayTag) : 'Anonymous player';\n render(row.rank, name, { you: row.isViewer });\n}\n```",
14177
+ "summary": "The same board, ranked over you and your accepted friends only.\n\nSame run type, ordering, buckets and `maxSize` as `get`, and the\nsame cached cadence — but ranks are positions **within this restricted\nboard**, not global ones. Your own rows carry\n[LeaderboardEntry.isViewer](https://gemarcade.com/developer/api/game#leaderboardentry-isviewer), which is what lets a standings screen\nmark \"you\"; on `get` that field is absent rather than false, because\na global board does not answer the question at all.\n\nA player with no friends gets a board of one — their own rows — not an\nerror. Character-owned runs are never on a friend board: friendship is a\nplayer-to-player relation.\n\n```js\nconst board = await gem.leaderboards.friends('time-trial');\nfor (const row of board.entries) {\n const name = row.displayTag ? text.toPlain(row.displayTag) : 'Anonymous player';\n render(row.rank, name, { you: row.isViewer });\n}\n```\n\nOn a `per_period` board this takes the same `periodsBack` and `period` as\n`get`. A friend in another time zone who is on the same date is on\nthe same board.",
13821
14178
  "deprecated": null,
13822
14179
  "params": [
13823
14180
  {
@@ -13834,7 +14191,7 @@
13834
14191
  }
13835
14192
  ],
13836
14193
  "returns": null,
13837
- "throws": "`not_found` when no board with this id is in the channel's active\n config, `invalid` when bucket values do not match the board's\n `bucket_by`, and `forbidden` in a context with no player — the friend\n view is a player surface, and a game server has no friends. (The global\n `get` declares no such refusal: it needs no viewer.)",
14194
+ "throws": "`not_found` when no board with this id is in the channel's active\n config, `invalid` when bucket values or a period do not match the board\n (as on `get`), and `forbidden` in a context with no player — the\n friend view is a player surface, and a game server has no friends. (The\n global `get` declares no such refusal: it needs no viewer.)",
13838
14195
  "example": null,
13839
14196
  "defaultValue": null,
13840
14197
  "guides": []
@@ -13847,7 +14204,7 @@
13847
14204
  "url": "https://gemarcade.com/developer/api/game#leaderboardsapi-mine",
13848
14205
  "signature": "mine(leaderboardType: string, options?: EntityLeaderboardOptions): Promise<EntityLeaderboard>",
13849
14206
  "documented": true,
13850
- "summary": "Your own runs on a board, best first, with your personal best.\n\nThis is the read for \"New personal best!\" and for a run-history panel. It\nis **not** a slice of the global board: `rank` is your position among your\nown runs, so `rank: 1` is your best — which is the number most games\nactually want and the one the global board cannot give you unless you\nhappen to be on it.\n\n```js\nconst mine = await gem.leaderboards.mine('time-trial');\nif (mine.personalBest) show(mine.personalBest.stats?.finish_ms);\n```\n\n**When to call it.** Not on a timer — there is no cadence to poll at, and\ndeliberately no `updateFrequencySeconds` on the result. Call it once a run\nhas been reported. ⚠️ Reporting a run is your dedicated server's job, or\nyour serverless function's, and your client has no happens-after edge with\na server: a read fired the instant a match ends can legitimately miss the\nrun just played. Read it when your server has told your client the run\nlanded, or once the function that recorded it has answered.\n\nAn empty result is the ordinary state of a player who has not played yet —\na `200` with no entries, not an error. If it is *always* empty, see\n[LeaderboardsApi](https://gemarcade.com/developer/api/game#leaderboardsapi) on where runs come from: a browser cannot submit\none.",
14207
+ "summary": "Your own runs on a board, best first, with your personal best.\n\nThis is the read for \"New personal best!\" and for a run-history panel. It\nis **not** a slice of the global board: `rank` is your position among your\nown runs, so `rank: 1` is your best — which is the number most games\nactually want and the one the global board cannot give you unless you\nhappen to be on it.\n\n```js\nconst mine = await gem.leaderboards.mine('time-trial');\nif (mine.personalBest) show(mine.personalBest.stats?.finish_ms);\n```\n\n**When to call it.** Not on a timer — there is no cadence to poll at, and\ndeliberately no `updateFrequencySeconds` on the result. Call it once a run\nhas been reported. ⚠️ Reporting a run is your dedicated server's job, or\nyour serverless function's, and your client has no happens-after edge with\na server: a read fired the instant a match ends can legitimately miss the\nrun just played. Read it when your server has told your client the run\nlanded, or once the function that recorded it has answered.\n\nAn empty result is the ordinary state of a player who has not played yet —\na `200` with no entries, not an error. If it is *always* empty, see\n[LeaderboardsApi](https://gemarcade.com/developer/api/game#leaderboardsapi) on where runs come from: a browser cannot submit\none.\n\nOn a `per_period` board the runs, the personal best and the total are ONE\nperiod's — the current one unless `periodsBack` or `period` names another\n— so `personalBest` is \"your best today\", and an empty result at the start\nof a day is the ordinary state.",
13851
14208
  "deprecated": null,
13852
14209
  "params": [
13853
14210
  {