@hopple/sdk 1.0.0-next.4 → 1.0.0-next.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/analytics.d.ts
1
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/analytics.d.ts
2
2
  /**
3
3
  * What a game says happened.
4
4
  *
@@ -29,7 +29,7 @@ type AnalyticsValue = string | number | boolean;
29
29
  /** @group Analytics */
30
30
  type AnalyticsProps = Record<string, AnalyticsValue>;
31
31
  //#endregion
32
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/errors.d.ts
32
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/errors.d.ts
33
33
  /**
34
34
  * How a call fails.
35
35
  *
@@ -115,7 +115,7 @@ declare class BridgeError extends Error {
115
115
  constructor(error: ProtocolError);
116
116
  }
117
117
  //#endregion
118
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/input.d.ts
118
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/input.d.ts
119
119
  /**
120
120
  * What the host says about how this game is being played.
121
121
  *
@@ -175,7 +175,7 @@ type InputBinding = {
175
175
  label?: string;
176
176
  };
177
177
  //#endregion
178
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/pause.d.ts
178
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/pause.d.ts
179
179
  /**
180
180
  * Whether the game is holding still, which the host may ask for and only the
181
181
  * game can answer.
@@ -233,7 +233,7 @@ type PauseState =
233
233
  */
234
234
  'unpausable';
235
235
  //#endregion
236
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/save.d.ts
236
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/save.d.ts
237
237
  /**
238
238
  * The one thing a game may keep.
239
239
  *
@@ -255,7 +255,8 @@ type PauseState =
255
255
  * game may assume anywhere, forever, because a game ships and is never updated.
256
256
  */
257
257
  /**
258
- * What a game may write anywhere, in bytes of `state` as UTF-8.
258
+ * What a game may write anywhere, in bytes: of `state` as UTF-8, or of the
259
+ * bytes a game handed over where it handed bytes.
259
260
  *
260
261
  * The floor rather than the cap: a host that will take more says so in the
261
262
  * welcome, and a game that never asks still knows this much is safe. 64 KiB is
@@ -284,7 +285,7 @@ type SaveStored = {
284
285
  bytes: number;
285
286
  };
286
287
  //#endregion
287
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/score.d.ts
288
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/score.d.ts
288
289
  /**
289
290
  * What the host says came of it.
290
291
  *
@@ -322,7 +323,7 @@ type ScoreStanding = {
322
323
  of?: number;
323
324
  };
324
325
  //#endregion
325
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/share.d.ts
326
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/share.d.ts
326
327
  /**
327
328
  * A link to this game, offered to the player by the host.
328
329
  *
@@ -376,7 +377,8 @@ type ScoreStanding = {
376
377
  * its first frame. See `ShareWelcome`.
377
378
  */
378
379
  /**
379
- * What every host carries, in bytes of `data` as UTF-8.
380
+ * What every host carries, in bytes of `data`: as UTF-8, or the bytes a game
381
+ * handed over where it handed bytes.
380
382
  *
381
383
  * The floor rather than the cap: a host may take more and none may take less,
382
384
  * so a game that stays under this is never refused for its size anywhere. It is
@@ -421,7 +423,7 @@ type ShareMeta = {
421
423
  [key: string]: ShareMetaValue;
422
424
  };
423
425
  //#endregion
424
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/methods.d.ts
426
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/methods.d.ts
425
427
  /**
426
428
  * How far a game has got with its own preparation.
427
429
  *
@@ -601,7 +603,7 @@ type DeviceInfo = {
601
603
  tier: DeviceTier;
602
604
  };
603
605
  //#endregion
604
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/listeners.d.ts
606
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/protocol/src/listeners.d.ts
605
607
  /**
606
608
  * The one way anything here is listened to: `on(listener)` hands back the
607
609
  * function that stops it.
@@ -630,7 +632,7 @@ type DeviceInfo = {
630
632
  */
631
633
  type Unsubscribe = () => void;
632
634
  //#endregion
633
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/manifest/src/manifest.d.ts
635
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/manifest/src/manifest.d.ts
634
636
  /**
635
637
  * The API a game is written against: `M.m`, a major and the least minor it
636
638
  * needs, or `next`, the pre-releases of the next stable release.
@@ -849,6 +851,26 @@ type ScoreBoard = {
849
851
  direction: ScoreDirection;
850
852
  /** See `ScoreFormat`. Absent means `integer`. */
851
853
  format?: ScoreFormat;
854
+ /**
855
+ * Which board, of the ones this id has named. Absent is `1`.
856
+ *
857
+ * **Raising it starts the board again from nobody.** The runs under the
858
+ * version before are kept and no longer counted, shown or ranked, so a board
859
+ * whose scores stopped being comparable — new levels, a new rule — begins
860
+ * again under the id the game already submits to.
861
+ *
862
+ * **Only a change to what a stored score means needs it.** The title changes
863
+ * freely, and so does the format between `integer` and `decimal`. Once the
864
+ * board holds scores, turning it around (`direction`) or writing it in
865
+ * another unit (to or from `duration-ms`) is refused at the same version.
866
+ * Going back to a version declared before brings its runs back, so it is
867
+ * held to what that version was.
868
+ *
869
+ * @asType integer
870
+ * @minimum 1
871
+ * @maximum 1000
872
+ */
873
+ version?: number;
852
874
  };
853
875
  /**
854
876
  * The boards this game keeps a score on.
@@ -1012,6 +1034,28 @@ type Achievement = {
1012
1034
  secret?: boolean;
1013
1035
  /** Present makes this measured rather than flagged. See `AchievementProgress`. */
1014
1036
  progress?: AchievementProgress;
1037
+ /**
1038
+ * Which achievement, of the ones this id has named. Absent is `1`.
1039
+ *
1040
+ * **Raising it starts the achievement again for everybody.** What players
1041
+ * reported and unlocked under the version before is kept and no longer
1042
+ * counted, so it is earned afresh under the id the game already reports to.
1043
+ *
1044
+ * **Only a change to what a stored value means needs it.** The words change
1045
+ * freely — title, detail, icon, secret — and so does the target, and a tier
1046
+ * added after the last one: progress is kept as the number reported, which
1047
+ * stays true when the goal moves, and a player who unlocked it keeps it. At
1048
+ * the same version, a published achievement may not turn from a flag into a
1049
+ * measure or back, turn its direction around, write its value in another
1050
+ * unit (to or from `duration-ms`), or move or drop a tier it already has.
1051
+ * Going back to a version declared before brings back what was earned under
1052
+ * it, so it is held to what that version was.
1053
+ *
1054
+ * @asType integer
1055
+ * @minimum 1
1056
+ * @maximum 1000
1057
+ */
1058
+ version?: number;
1015
1059
  };
1016
1060
  /**
1017
1061
  * The achievements this game keeps for a player.
@@ -1540,6 +1584,8 @@ type GameManifest = {
1540
1584
  publishable?: boolean;
1541
1585
  /**
1542
1586
  * The game's own version, `MAJOR.MINOR.PATCH`. Ours to read, never to parse for meaning.
1587
+ * People see it beside the build the server numbers each upload with, and
1588
+ * each published version needs one no other published version of the game has.
1543
1589
  *
1544
1590
  * @category Important
1545
1591
  * @pattern ^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$
@@ -1648,53 +1694,6 @@ type GameManifest = {
1648
1694
  */
1649
1695
  previews?: PreviewsDeclaration;
1650
1696
  };
1651
- /**
1652
- * A game project's hopple config: `hopple.config.ts`, `hopple.config.js` or
1653
- * `hopple.config.json`, beside the project's `package.json`.
1654
- *
1655
- * Everything the `hopple` command line needs to know about a project, in one
1656
- * place: how to build the game, which built file is the game, and the manifest
1657
- * that says what the game is. Only the manifest leaves the machine, uploaded
1658
- * beside the page as `game.manifest.json`. A TypeScript or JavaScript config
1659
- * default-exports one of these; a JSON config is one.
1660
- *
1661
- * ```ts
1662
- * import type { HoppleConfig } from '@hopple/sdk';
1663
- *
1664
- * export default {
1665
- * build: 'npm run build',
1666
- * page: 'dist/index.html',
1667
- * manifest: { id: 'my-game', version: '0.1.0', title: 'My Game', orientation: 'portrait', audio: null },
1668
- * } satisfies HoppleConfig;
1669
- * ```
1670
- *
1671
- * @group Manifest
1672
- * @category Important
1673
- */
1674
- type HoppleConfig = {
1675
- /** Where an editor finds this file's schema, in a JSON config. Read by nothing else. */
1676
- $schema?: string;
1677
- /**
1678
- * The command that builds the game, run in the config's directory before
1679
- * `hopple dev`, `check` and `publish` use it, so none of them sees a stale
1680
- * build. Leave it out for a game with nothing to build.
1681
- *
1682
- * @pattern \S
1683
- * @errorMessage must be the command that builds the game, like "npm run build"
1684
- */
1685
- build?: string;
1686
- /**
1687
- * The built page, relative to the config. The directory it is in is
1688
- * published, with the page as its `index.html`; a page beside its config is
1689
- * published alone, so it has to hold the whole game.
1690
- *
1691
- * @pattern \.[Hh][Tt][Mm][Ll]?$
1692
- * @errorMessage must be the built .html file, like "dist/index.html"
1693
- */
1694
- page: string;
1695
- /** What the game is. Uploaded beside the page, and read by every host before the game runs. */
1696
- manifest: ProjectManifest;
1697
- };
1698
1697
  /**
1699
1698
  * The manifest as a project's config holds it: `api` may be left out, for the
1700
1699
  * CLI to fill from the installed `@hopple/sdk`.
@@ -1706,7 +1705,7 @@ type ProjectManifest = Omit<GameManifest, 'api'> & {
1706
1705
  api?: ApiVersion;
1707
1706
  };
1708
1707
  //#endregion
1709
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/achievements.d.ts
1708
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/achievements.d.ts
1710
1709
  /**
1711
1710
  * Where this player stands on one achievement.
1712
1711
  *
@@ -1804,7 +1803,7 @@ type Achievements<Id extends string = string, Measured extends string = Id> = {
1804
1803
  progress(id: Measured, value: number): void;
1805
1804
  };
1806
1805
  //#endregion
1807
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/analytics.d.ts
1806
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/analytics.d.ts
1808
1807
  /**
1809
1808
  * @group Analytics
1810
1809
  * @category Important
@@ -1821,7 +1820,7 @@ type Analytics<Events extends object = Record<string, AnalyticsProps>> = {
1821
1820
  track<Event extends keyof Events & string>(name: Event, props?: Events[Event]): void;
1822
1821
  };
1823
1822
  //#endregion
1824
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/audio.d.ts
1823
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/audio.d.ts
1825
1824
  /**
1826
1825
  * Something about the game's sound changed: the host's order, the player's
1827
1826
  * mute, a fader. Read what you need — `volume(channel)`, `allowed` — and apply
@@ -1912,7 +1911,7 @@ type Audio<Channel extends string = string> = {
1912
1911
  media(element: HTMLMediaElement, channel?: Channel): Unsubscribe;
1913
1912
  };
1914
1913
  //#endregion
1915
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/info.d.ts
1914
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/info.d.ts
1916
1915
  /**
1917
1916
  * What the host said, frozen.
1918
1917
  *
@@ -1930,7 +1929,7 @@ type Info = Readonly<{
1930
1929
  device: Readonly<DeviceInfo>;
1931
1930
  }>;
1932
1931
  //#endregion
1933
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/layout.d.ts
1932
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/layout.d.ts
1934
1933
  /**
1935
1934
  * How much of each edge belongs to the host. CSS pixels.
1936
1935
  *
@@ -2040,7 +2039,7 @@ type InsetPolicy = {
2040
2039
  */
2041
2040
  declare function insetsFrom(obstructions: readonly Obstruction[], frame: Size, policy?: InsetPolicy): Insets;
2042
2041
  //#endregion
2043
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/names.d.ts
2042
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/names.d.ts
2044
2043
  /**
2045
2044
  * The keys of a declared map, or `string` where the map is not a literal one —
2046
2045
  * which is what a manifest typed as the wide `GameManifest` gives — and
@@ -2160,7 +2159,7 @@ type EventProps<M> = M extends {
2160
2159
  */
2161
2160
  type StickName = 'pad.left.stick' | 'pad.right.stick' | 'pad.stick' | `controls.${string}`;
2162
2161
  //#endregion
2163
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/state.d.ts
2162
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/input/state.d.ts
2164
2163
  /**
2165
2164
  * Where the pointer is, in the frame's own coordinates.
2166
2165
  *
@@ -2221,7 +2220,7 @@ type InputSnapshot<Action extends string = string> = {
2221
2220
  pointer: PointerState | null;
2222
2221
  };
2223
2222
  //#endregion
2224
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/players.d.ts
2223
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/input/players.d.ts
2225
2224
  /**
2226
2225
  * What one player holds: a pad, one part of the keyboard, or the on-screen
2227
2226
  * controls.
@@ -2243,7 +2242,7 @@ type Controller =
2243
2242
  kind: 'touch';
2244
2243
  };
2245
2244
  //#endregion
2246
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/index.d.ts
2245
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/input/index.d.ts
2247
2246
  /**
2248
2247
  * A binding a game can name on its own, for a prompt about a button that is
2249
2248
  * not one of its actions: `pad.start`, `key.Space`.
@@ -2480,7 +2479,7 @@ type Controls<Layout extends string = string> = {
2480
2479
  readonly available: boolean;
2481
2480
  };
2482
2481
  //#endregion
2483
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/lifecycle.d.ts
2482
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/lifecycle.d.ts
2484
2483
  /** @group Lifecycle */
2485
2484
  type LifecycleListener = (phase: Phase, previous: Phase) => void | Promise<void>;
2486
2485
  /**
@@ -2499,7 +2498,7 @@ type Lifecycle = {
2499
2498
  on(listener: LifecycleListener): Unsubscribe;
2500
2499
  };
2501
2500
  //#endregion
2502
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/loop.d.ts
2501
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/loop.d.ts
2503
2502
  /**
2504
2503
  * The host has let the game run its loop, or stopped it. Return a promise to
2505
2504
  * make the host wait.
@@ -2553,7 +2552,7 @@ type Loop = {
2553
2552
  tick(frame: FrameCallback): Unsubscribe;
2554
2553
  };
2555
2554
  //#endregion
2556
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/panel.d.ts
2555
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/panel.d.ts
2557
2556
  /**
2558
2557
  * The pages of the host's panel a game has a name for, as the keys of this
2559
2558
  * interface.
@@ -2691,7 +2690,7 @@ type Panel = {
2691
2690
  open(page: PageId): void;
2692
2691
  };
2693
2692
  //#endregion
2694
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/pause.d.ts
2693
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/pause.d.ts
2695
2694
  /**
2696
2695
  * The host has asked for a pause, or for the game to carry on.
2697
2696
  *
@@ -2752,7 +2751,7 @@ type Pause = {
2752
2751
  on(listener: PauseListener): Unsubscribe;
2753
2752
  };
2754
2753
  //#endregion
2755
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/resources.d.ts
2754
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/resources.d.ts
2756
2755
  /**
2757
2756
  * The host has let the game hold its resources again, or asked for them back.
2758
2757
  * Return a promise to make the host wait until they have gone, or come back.
@@ -2782,7 +2781,7 @@ type Resources = {
2782
2781
  on(listener: ResourcesListener): Unsubscribe;
2783
2782
  };
2784
2783
  //#endregion
2785
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/save.d.ts
2784
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/save.d.ts
2786
2785
  /**
2787
2786
  * What a game gets back from a read: its own schema number, and its state.
2788
2787
  *
@@ -2792,7 +2791,10 @@ type Resources = {
2792
2791
  type SaveState = {
2793
2792
  /** Whatever the game passed to `write`, or 1 where it never named one. */
2794
2793
  version: number;
2795
- /** Parsed where it was written as a value, verbatim where it was a string. */
2794
+ /**
2795
+ * Parsed where it was written as a value, verbatim where it was a string, and
2796
+ * a `Uint8Array` where it was one.
2797
+ */
2796
2798
  state: unknown;
2797
2799
  };
2798
2800
  /**
@@ -2832,20 +2834,41 @@ type SaveListener = (now: SaveStatus, previous: SaveStatus) => void;
2832
2834
  *
2833
2835
  * Pure: it is called with two states and must not touch the game's own. Run
2834
2836
  * while the game loads, its answer is what `read()` resolves with; run while
2835
- * the game plays, its answer goes to `onMerge` as well.
2837
+ * the game plays, its answer goes to `onChange` as well.
2836
2838
  *
2837
2839
  * @group Save
2838
2840
  * @category Useful
2839
2841
  */
2840
2842
  type SaveMerger = (mine: SaveState, theirs: SaveState) => SaveState;
2841
2843
  /**
2842
- * Told the merged save, once a merge has run while the game was playing on
2843
- * its own: play on from this, which is both devices' progress.
2844
+ * Why the save a game plays on changed under it.
2845
+ *
2846
+ * - `arrived`: another device's save, built on what the game had — a server
2847
+ * that answered after the game read, or another device that played on. Run
2848
+ * through the game's `merge` with what it last saved, where it has one.
2849
+ * - `merged`: the game's `merge` made one save of this device's and another
2850
+ * that went their own ways.
2851
+ * - `gone`: thrown away on another device, with nothing made here since. The
2852
+ * save is `null`.
2853
+ *
2854
+ * @group Save
2855
+ * @category Useful
2856
+ */
2857
+ type SaveChangeReason = 'arrived' | 'merged' | 'gone';
2858
+ /**
2859
+ * Told the save the game now plays on, once it changed under it while it
2860
+ * played, and why: play on from this. `null` is a save thrown away on another
2861
+ * device (`gone`): start again.
2862
+ *
2863
+ * The host now holds what the listener is handed, and the game's next write
2864
+ * continues it. To keep the game's own instead — progress it has not saved
2865
+ * yet, or a New game the player has just chosen over a save that arrived
2866
+ * after — write it from the listener: it takes that save's place.
2844
2867
  *
2845
2868
  * @group Save
2846
2869
  * @category Useful
2847
2870
  */
2848
- type SaveMergeListener = (merged: SaveState) => void;
2871
+ type SaveChangeListener = (save: SaveState | null, why: SaveChangeReason) => void;
2849
2872
  /**
2850
2873
  * What a game says about its save at `init`, before the host has asked
2851
2874
  * anything.
@@ -2891,10 +2914,12 @@ type Save = {
2891
2914
  /**
2892
2915
  * Keep this. Resolves when the host says the record is durable.
2893
2916
  *
2894
- * A string is stored as it stands and comes back a string; anything else is
2895
- * `JSON.stringify`d and comes back parsed. A value that cannot survive that
2896
- * at all — a cycle, a `BigInt` — rejects with `bad_params` before anything
2897
- * reaches the bridge.
2917
+ * A string is stored as it stands and comes back a string; a `Uint8Array` is
2918
+ * stored as its bytes and comes back a `Uint8Array`, `maxBytes` counting the
2919
+ * bytes themselves; anything else is `JSON.stringify`d and comes back parsed.
2920
+ * A value that cannot survive that at all — a cycle, a `BigInt` — rejects
2921
+ * with `bad_params` before anything reaches the bridge, and bytes on a host
2922
+ * older than keeping them reject with `unsupported`.
2898
2923
  *
2899
2924
  * `version` is the game's own schema number and means whatever the game says
2900
2925
  * it means. There is no migration hook: a hook would be frozen game-side code
@@ -2907,19 +2932,31 @@ type Save = {
2907
2932
  /** Throw the save away — a game resetting its own progress, at a player's asking. */
2908
2933
  clear(): Promise<void>;
2909
2934
  /**
2910
- * Told when the game's `merge` ran while the game was playing: what the
2911
- * host now keeps is the merged save, and the game should play on from it.
2935
+ * Told when the save the game plays on changed under it, and why: play on
2936
+ * from what the listener is handed.
2912
2937
  *
2913
- * A merge needed while the game plays waits for its next load unless
2914
- * something is listening here, because a game that went on playing from its
2915
- * own state would save over the merge with its next write.
2938
+ * That is another device's save arriving after the game read — a server
2939
+ * that answered late, a page left open while another device played on —
2940
+ * (`arrived`), or the game's own `merge` run while it played (`merged`), or
2941
+ * the save thrown away on another device with nothing made here since
2942
+ * (`gone`, `null`). Without a merge, an arrival is handed over as it is: it
2943
+ * is built on what the game had.
2944
+ *
2945
+ * Listening is what lets a game start before its save is in hand: draw from
2946
+ * nothing at once, and take the player's save when it comes. Register it
2947
+ * before `read()`. A game that is not listening is never written over —
2948
+ * what it writes next is kept beside what arrived, for its merge or the
2949
+ * player to settle.
2950
+ *
2951
+ * To keep the game's own over what it is handed, write it from the
2952
+ * listener: that write takes the handed save's place.
2916
2953
  */
2917
- onMerge(listener: SaveMergeListener): Unsubscribe;
2954
+ onChange(listener: SaveChangeListener): Unsubscribe;
2918
2955
  /** The largest save this host will take, in bytes. `SAVE_BYTES` where it said nothing. */
2919
2956
  readonly maxBytes: number;
2920
2957
  };
2921
2958
  //#endregion
2922
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/score.d.ts
2959
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/score.d.ts
2923
2960
  /**
2924
2961
  * Where this player stands on every board, as far as anybody has said.
2925
2962
  *
@@ -2996,7 +3033,7 @@ type Score<Board extends string = string> = {
2996
3033
  }): Promise<ScoreResult>;
2997
3034
  };
2998
3035
  //#endregion
2999
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/share.d.ts
3036
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/share.d.ts
3000
3037
  /**
3001
3038
  * @group Share
3002
3039
  * @category Important
@@ -3023,9 +3060,18 @@ type Share = {
3023
3060
  * check it — the same care `save.read` deserves, for a stronger reason.
3024
3061
  *
3025
3062
  * **`null` says nothing about whether a link can be offered.** It means
3026
- * nobody arrived by one; `available` is what says whether one can be made.
3063
+ * nobody arrived by one, or that the link carried bytes, which are `bytes`;
3064
+ * `available` is what says whether one can be made.
3027
3065
  */
3028
3066
  readonly data: string | null;
3067
+ /**
3068
+ * What the link that opened this game carried, where some run of this game
3069
+ * passed `offer` a `Uint8Array`: those bytes. `null` otherwise — a player who
3070
+ * arrived the ordinary way, or a link that carried a string, which is `data`.
3071
+ *
3072
+ * A stranger's input, exactly as `data` is.
3073
+ */
3074
+ readonly bytes: Uint8Array | null;
3029
3075
  /**
3030
3076
  * Ask the host to put a link to this game in front of the player.
3031
3077
  *
@@ -3035,23 +3081,26 @@ type Share = {
3035
3081
  * may keep a sheet open as long as they like, and pressing Share again while
3036
3082
  * one is up does not open a second.
3037
3083
  *
3038
- * `data` is the game's own and travels in the link, opaque to the host, back
3039
- * to whoever opens it. A string, because it has to survive being a URL and
3040
- * because the game reading it back is the game that wrote it: anything
3041
- * structured is the game's to encode and the game's to parse. `SHARE_BYTES`
3042
- * is what every host carries; more than that is a host's own decision, and a
3043
- * host that will not carry it refuses with `too_large`.
3084
+ * `data` is the game's own and travels with the link, opaque to the host,
3085
+ * back to whoever opens it: a string, which arrives as `share.data`, or a
3086
+ * `Uint8Array`, which arrives as `share.bytes`. The game reading it back is
3087
+ * the game that wrote it, so anything structured is the game's to encode and
3088
+ * the game's to parse — bytes pack a board far tighter than text does.
3089
+ * `SHARE_BYTES` is what every host carries, counted in bytes either way; more
3090
+ * than that is a host's own decision, and a host that will not carry it
3091
+ * refuses with `too_large`.
3044
3092
  *
3045
3093
  * `meta` is for the platform rather than for the game: why this link was made
3046
3094
  * — the level, the score, which of the game's buttons — kept beside the
3047
3095
  * host's own record of who made it and how, so a game's makers can learn
3048
3096
  * which of its shares bring people in. Anything JSON can say, and
3049
3097
  * `SHARE_META_BYTES` of it is what every host takes. Nobody who opens the link
3050
- * is handed it.
3098
+ * is handed it; the game's team reads it, with `data`, by `hopple share`.
3051
3099
  *
3052
3100
  * What goes wrong is reported to `onError` and sends nothing: `bad_params`
3053
- * for a `data` that is not a string or a `meta` JSON cannot carry, which are
3054
- * bugs in the game; `too_large` from a host for more than it carries;
3101
+ * for a `data` that is neither a string nor a `Uint8Array`, or a `meta` JSON
3102
+ * cannot carry, which are bugs in the game; `unsupported` for bytes on a host
3103
+ * older than carrying them; `too_large` from a host for more than it carries;
3055
3104
  * `rate_limited` from one that will not offer a link right now. A host with
3056
3105
  * no link to give does nothing, which a game checking `available` never asks
3057
3106
  * of it. Every one of them is a game that carries on: nothing about a share
@@ -3063,12 +3112,12 @@ type Share = {
3063
3112
  * sheet on screen comes from the player's own gesture.
3064
3113
  */
3065
3114
  offer(options?: {
3066
- data?: string;
3115
+ data?: string | Uint8Array;
3067
3116
  meta?: ShareMeta;
3068
3117
  }): void;
3069
3118
  };
3070
3119
  //#endregion
3071
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/init.d.ts
3120
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/game/src/init.d.ts
3072
3121
  /**
3073
3122
  * What `init` hands back: every namespace a game has, on one object.
3074
3123
  *
@@ -3364,4 +3413,4 @@ type InitOptions = {
3364
3413
  */
3365
3414
  declare function init<M extends object = GameManifest>(options?: InitOptions): Promise<GameSdk<M>>;
3366
3415
  //#endregion
3367
- export { type Achievement, type AchievementDeclaration, type AchievementListener, type AchievementName, type AchievementProgress, type AchievementStanding, type AchievementStatus, type AchievementTier, type Achievements, type ActionListener, type ActionName, type Analytics, type AnalyticsDeclaration, type AnalyticsProps, type AnalyticsShape, type AnalyticsType, type AnalyticsValue, type ApiVersion, type AppInfo, type AppSurface, type Audio, type AudioChannel, type AudioDeclaration, type AudioListener, type BoardName, BridgeError, type ButtonName, type ChannelName, type ControlLayout, type ControlShape, type ControlSlot, type ControlZone, type Controller, type Controls, type DeclaredNames, type DeclaredValue, type DeviceInfo, type DeviceTier, type ErrorCode, type ErrorCodes, type EventName, type EventProps, type FrameCallback, type GameInfo, type GameManifest, type GameSdk, type HoppleConfig, type Info, type InitOptions, type Input, type InputAction, type InputBinding, type InputDeclaration, type InputDevice, type InputListener, type InputScheme, type InputSnapshot, type InputState, type InsetMode, type InsetPolicy, type Insets, type Layout, type LayoutDeclaration, type LayoutListener, type LayoutName, type LayoutState, type Lifecycle, type LifecycleListener, type Loop, type LoopListener, MAX_STEP, type MeasuredName, type Obstruction, type ObstructionKind, type Orientation, type PageId, type Panel, type PanelListener, type PanelPages, type PanelState, type Pause, type PauseListener, type PauseState, type Phase, type Player, type PlayerActionName, type PlayerInfo, type PointerListener, type PointerState, type ProjectManifest, type Rect, type Resources, type ResourcesListener, SAVE_BYTES, SHARE_BYTES, SHARE_META_BYTES, type Save, type SaveDeclaration, type SaveListener, type SaveMergeListener, type SaveMerger, type SaveSetup, type SaveState, type SaveStatus, type SaveStored, type Score, type ScoreBoard, type ScoreDeclaration, type ScoreDirection, type ScoreFormat, type ScoreListener, type ScoreResult, type ScoreStanding, type ScoreStandings, type Share, type ShareMeta, type ShareMetaValue, type Size, type StickListener, type StickName, type StickState, type Unsubscribe, init, insetsFrom };
3416
+ export { type Achievement, type AchievementDeclaration, type AchievementListener, type AchievementName, type AchievementProgress, type AchievementStanding, type AchievementStatus, type AchievementTier, type Achievements, type ActionListener, type ActionName, type Analytics, type AnalyticsDeclaration, type AnalyticsProps, type AnalyticsShape, type AnalyticsType, type AnalyticsValue, type ApiVersion, type AppInfo, type AppSurface, type Audio, type AudioChannel, type AudioDeclaration, type AudioListener, type BoardName, BridgeError, type ButtonName, type ChannelName, type ControlLayout, type ControlShape, type ControlSlot, type ControlZone, type Controller, type Controls, type DeclaredNames, type DeclaredValue, type DeviceInfo, type DeviceTier, type ErrorCode, type ErrorCodes, type EventName, type EventProps, type FrameCallback, type GameInfo, type GameManifest, type GameSdk, type Info, type InitOptions, type Input, type InputAction, type InputBinding, type InputDeclaration, type InputDevice, type InputListener, type InputScheme, type InputSnapshot, type InputState, type InsetMode, type InsetPolicy, type Insets, type Layout, type LayoutDeclaration, type LayoutListener, type LayoutName, type LayoutState, type Lifecycle, type LifecycleListener, type Loop, type LoopListener, MAX_STEP, type MeasuredName, type Obstruction, type ObstructionKind, type Orientation, type PageId, type Panel, type PanelListener, type PanelPages, type PanelState, type Pause, type PauseListener, type PauseState, type Phase, type Player, type PlayerActionName, type PlayerInfo, type PointerListener, type PointerState, type ProjectManifest, type Rect, type Resources, type ResourcesListener, SAVE_BYTES, SHARE_BYTES, SHARE_META_BYTES, type Save, type SaveChangeListener, type SaveChangeReason, type SaveDeclaration, type SaveListener, type SaveMerger, type SaveSetup, type SaveState, type SaveStatus, type SaveStored, type Score, type ScoreBoard, type ScoreDeclaration, type ScoreDirection, type ScoreFormat, type ScoreListener, type ScoreResult, type ScoreStanding, type ScoreStandings, type Share, type ShareMeta, type ShareMetaValue, type Size, type StickListener, type StickName, type StickState, type Unsubscribe, init, insetsFrom };
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @hopple/sdk 1.0.0-next.4 | Copyright (c) 2026 The Hopple authors | MIT License */
1
+ /*! @hopple/sdk 1.0.0-next.5 | Copyright (c) 2026 The Hopple authors | MIT License */
2
2
  // The SDK runs from the runtime the page loads as its first script, which
3
3
  // defines the global `Hopple`; this forwards each export to it.
4
4
  const runtime = globalThis.Hopple;
package/manifest.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/manifest/src/manifest.d.ts
1
+ //#region ../../../../../../../tmp/hopple-sdk-types-ky983p/manifest/src/manifest.d.ts
2
2
  /**
3
3
  * The API a game is written against: `M.m`, a major and the least minor it
4
4
  * needs, or `next`, the pre-releases of the next stable release.
@@ -217,6 +217,26 @@ type ScoreBoard = {
217
217
  direction: ScoreDirection;
218
218
  /** See `ScoreFormat`. Absent means `integer`. */
219
219
  format?: ScoreFormat;
220
+ /**
221
+ * Which board, of the ones this id has named. Absent is `1`.
222
+ *
223
+ * **Raising it starts the board again from nobody.** The runs under the
224
+ * version before are kept and no longer counted, shown or ranked, so a board
225
+ * whose scores stopped being comparable — new levels, a new rule — begins
226
+ * again under the id the game already submits to.
227
+ *
228
+ * **Only a change to what a stored score means needs it.** The title changes
229
+ * freely, and so does the format between `integer` and `decimal`. Once the
230
+ * board holds scores, turning it around (`direction`) or writing it in
231
+ * another unit (to or from `duration-ms`) is refused at the same version.
232
+ * Going back to a version declared before brings its runs back, so it is
233
+ * held to what that version was.
234
+ *
235
+ * @asType integer
236
+ * @minimum 1
237
+ * @maximum 1000
238
+ */
239
+ version?: number;
220
240
  };
221
241
  /**
222
242
  * The boards this game keeps a score on.
@@ -380,6 +400,28 @@ type Achievement = {
380
400
  secret?: boolean;
381
401
  /** Present makes this measured rather than flagged. See `AchievementProgress`. */
382
402
  progress?: AchievementProgress;
403
+ /**
404
+ * Which achievement, of the ones this id has named. Absent is `1`.
405
+ *
406
+ * **Raising it starts the achievement again for everybody.** What players
407
+ * reported and unlocked under the version before is kept and no longer
408
+ * counted, so it is earned afresh under the id the game already reports to.
409
+ *
410
+ * **Only a change to what a stored value means needs it.** The words change
411
+ * freely — title, detail, icon, secret — and so does the target, and a tier
412
+ * added after the last one: progress is kept as the number reported, which
413
+ * stays true when the goal moves, and a player who unlocked it keeps it. At
414
+ * the same version, a published achievement may not turn from a flag into a
415
+ * measure or back, turn its direction around, write its value in another
416
+ * unit (to or from `duration-ms`), or move or drop a tier it already has.
417
+ * Going back to a version declared before brings back what was earned under
418
+ * it, so it is held to what that version was.
419
+ *
420
+ * @asType integer
421
+ * @minimum 1
422
+ * @maximum 1000
423
+ */
424
+ version?: number;
383
425
  };
384
426
  /**
385
427
  * The achievements this game keeps for a player.
@@ -902,6 +944,8 @@ type GameManifest = {
902
944
  publishable?: boolean;
903
945
  /**
904
946
  * The game's own version, `MAJOR.MINOR.PATCH`. Ours to read, never to parse for meaning.
947
+ * People see it beside the build the server numbers each upload with, and
948
+ * each published version needs one no other published version of the game has.
905
949
  *
906
950
  * @category Important
907
951
  * @pattern ^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$