@hopple/sdk 1.0.0-next.12 → 1.0.0-next.13

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-hbSZtA/protocol/src/analytics.d.ts
1
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/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-hbSZtA/protocol/src/errors.d.ts
32
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/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-hbSZtA/protocol/src/input.d.ts
118
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/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-hbSZtA/protocol/src/pause.d.ts
178
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/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-hbSZtA/protocol/src/save.d.ts
236
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/protocol/src/save.d.ts
237
237
  /**
238
238
  * The one thing a game may keep.
239
239
  *
@@ -285,7 +285,7 @@ type SaveStored = {
285
285
  bytes: number;
286
286
  };
287
287
  //#endregion
288
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/protocol/src/score.d.ts
288
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/protocol/src/score.d.ts
289
289
  /**
290
290
  * What the host says came of it.
291
291
  *
@@ -323,7 +323,7 @@ type ScoreStanding = {
323
323
  of?: number;
324
324
  };
325
325
  //#endregion
326
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/protocol/src/share.d.ts
326
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/protocol/src/share.d.ts
327
327
  /**
328
328
  * A link to this game, offered to the player by the host.
329
329
  *
@@ -423,7 +423,7 @@ type ShareMeta = {
423
423
  [key: string]: ShareMetaValue;
424
424
  };
425
425
  //#endregion
426
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/protocol/src/methods.d.ts
426
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/protocol/src/methods.d.ts
427
427
  /**
428
428
  * How far a game has got with its own preparation.
429
429
  *
@@ -603,7 +603,7 @@ type DeviceInfo = {
603
603
  tier: DeviceTier;
604
604
  };
605
605
  //#endregion
606
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/protocol/src/listeners.d.ts
606
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/protocol/src/listeners.d.ts
607
607
  /**
608
608
  * The one way anything here is listened to: `on(listener)` hands back the
609
609
  * function that stops it.
@@ -632,7 +632,7 @@ type DeviceInfo = {
632
632
  */
633
633
  type Unsubscribe = () => void;
634
634
  //#endregion
635
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/manifest/src/manifest.d.ts
635
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/manifest/src/manifest.d.ts
636
636
  /**
637
637
  * The API a game is written against: `M.m`, a major and the least minor it
638
638
  * needs, or `next`, the pre-releases of the next stable release.
@@ -1534,6 +1534,36 @@ type PreviewsDeclaration = {
1534
1534
  * @category Useful
1535
1535
  */
1536
1536
  type GameGenre = 'racing' | 'shooter' | 'platformer' | 'puzzle' | 'arcade' | 'physics' | 'roguelike' | 'strategy' | 'building' | 'idle' | 'sports' | 'horror' | 'stealth' | 'fighting' | 'casual' | 'simulation' | 'adventure' | 'rhythm' | 'rpg' | 'local-multiplayer' | 'online-multiplayer';
1537
+ /**
1538
+ * A few seconds of real play, which Explore shows in place of the still cover,
1539
+ * played muted, over and over. See `GameManifest.clip`.
1540
+ *
1541
+ * @group Manifest
1542
+ * @category Useful
1543
+ */
1544
+ type ClipDeclaration = {
1545
+ /**
1546
+ * The clip: the path of an MP4 file beside the config. H.264 with no sound
1547
+ * track, 2 to 15 seconds long, its longest side at most 1280 and its
1548
+ * shortest at most 720, saved with "fast start", and at most
1549
+ * `CLIP_MAX_BYTES`. `clipProblems` says what is wrong with one.
1550
+ *
1551
+ * @pattern ^(?!/)(?!.*\.\.)[A-Za-z0-9._/-]+\.mp4$
1552
+ * @maxLength 200
1553
+ * @errorMessage must be the path of an .mp4 file beside the config, like "clip.mp4"
1554
+ */
1555
+ video: string;
1556
+ /**
1557
+ * The clip's first frame, as a PNG, JPEG or WebP file beside the config,
1558
+ * shown until the clip plays and wherever it cannot. At most
1559
+ * `POSTER_MAX_BYTES`.
1560
+ *
1561
+ * @pattern ^(?!/)(?!.*\.\.)[A-Za-z0-9._/-]+\.(?:png|jpe?g|webp)$
1562
+ * @maxLength 200
1563
+ * @errorMessage must be the path of a .png, .jpg or .webp file beside the config, like "clip.png"
1564
+ */
1565
+ poster: string;
1566
+ };
1537
1567
  /**
1538
1568
  * Publishing refuses a key the manifest's API does not know; a host reading a
1539
1569
  * published one ignores it (see `validateManifest`).
@@ -1621,20 +1651,34 @@ type GameManifest = {
1621
1651
  */
1622
1652
  genres?: GameGenre[];
1623
1653
  /**
1624
- * A picture of the game, shown wherever a link to it is pasted — a chat, a
1625
- * post, a message.
1654
+ * A picture of the game: its cover on Explore's shelves and in a player's
1655
+ * inbox, and what a link to it shows wherever it is pasted — a chat, a post,
1656
+ * a message.
1626
1657
  *
1627
1658
  * The path of a PNG, JPEG or WebP file, relative to the config the manifest
1628
1659
  * is in, and at most `COVER_MAX_BYTES`. `hopple publish` uploads it beside
1629
- * the game. 1200 × 630 is the shape every platform shows whole; without one,
1630
- * a link shows Hopple's own card.
1660
+ * the game, and a server takes no upload without one: only a game that is
1661
+ * never uploaded leaves it out. 1200 × 630 is the shape every platform shows
1662
+ * whole; `hopple smoke --cover` makes one from the game itself.
1631
1663
  *
1632
- * @category Useful
1664
+ * @category Important
1633
1665
  * @pattern ^(?!/)(?!.*\.\.)[A-Za-z0-9._/-]+\.(?:png|jpe?g|webp)$
1634
1666
  * @maxLength 200
1635
1667
  * @errorMessage must be the path of a .png, .jpg or .webp file beside the config, like "cover.png"
1636
1668
  */
1637
1669
  cover?: string;
1670
+ /**
1671
+ * A few seconds of real play, which Explore shows in place of the still
1672
+ * `cover`, played muted. `poster` is the clip's first frame, shown until it
1673
+ * plays. The clip is an H.264 MP4 with no sound track, 2 to 15 seconds long,
1674
+ * no larger than 1280 × 720 either way up, saved with "fast start", and at
1675
+ * most `CLIP_MAX_BYTES`; the poster at most `POSTER_MAX_BYTES`. Both are
1676
+ * paths beside the config, and `hopple publish` uploads them with the game.
1677
+ * See `ClipDeclaration`.
1678
+ *
1679
+ * @category Useful
1680
+ */
1681
+ clip?: ClipDeclaration;
1638
1682
  /** @category Important */
1639
1683
  orientation: Orientation;
1640
1684
  /**
@@ -1705,7 +1749,7 @@ type ProjectManifest = Omit<GameManifest, 'api'> & {
1705
1749
  api?: ApiVersion;
1706
1750
  };
1707
1751
  //#endregion
1708
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/achievements.d.ts
1752
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/achievements.d.ts
1709
1753
  /**
1710
1754
  * Where this player stands on one achievement.
1711
1755
  *
@@ -1803,7 +1847,7 @@ type Achievements<Id extends string = string, Measured extends string = Id> = {
1803
1847
  progress(id: Measured, value: number): void;
1804
1848
  };
1805
1849
  //#endregion
1806
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/analytics.d.ts
1850
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/analytics.d.ts
1807
1851
  /**
1808
1852
  * @group Analytics
1809
1853
  * @category Important
@@ -1820,7 +1864,7 @@ type Analytics<Events extends object = Record<string, AnalyticsProps>> = {
1820
1864
  track<Event extends keyof Events & string>(name: Event, props?: Events[Event]): void;
1821
1865
  };
1822
1866
  //#endregion
1823
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/audio.d.ts
1867
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/audio.d.ts
1824
1868
  /**
1825
1869
  * Something about the game's sound changed: the host's order, the player's
1826
1870
  * mute, a fader. Read what you need — `volume(channel)`, `allowed` — and apply
@@ -1911,7 +1955,7 @@ type Audio<Channel extends string = string> = {
1911
1955
  media(element: HTMLMediaElement, channel?: Channel): Unsubscribe;
1912
1956
  };
1913
1957
  //#endregion
1914
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/info.d.ts
1958
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/info.d.ts
1915
1959
  /**
1916
1960
  * What the host said, frozen.
1917
1961
  *
@@ -1929,7 +1973,7 @@ type Info = Readonly<{
1929
1973
  device: Readonly<DeviceInfo>;
1930
1974
  }>;
1931
1975
  //#endregion
1932
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/layout.d.ts
1976
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/layout.d.ts
1933
1977
  /**
1934
1978
  * How much of each edge belongs to the host. CSS pixels.
1935
1979
  *
@@ -2039,7 +2083,7 @@ type InsetPolicy = {
2039
2083
  */
2040
2084
  declare function insetsFrom(obstructions: readonly Obstruction[], frame: Size, policy?: InsetPolicy): Insets;
2041
2085
  //#endregion
2042
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/names.d.ts
2086
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/names.d.ts
2043
2087
  /**
2044
2088
  * The keys of a declared map, or `string` where the map is not a literal one —
2045
2089
  * which is what a manifest typed as the wide `GameManifest` gives — and
@@ -2159,7 +2203,7 @@ type EventProps<M> = M extends {
2159
2203
  */
2160
2204
  type StickName = 'pad.left.stick' | 'pad.right.stick' | 'pad.stick' | `controls.${string}`;
2161
2205
  //#endregion
2162
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/input/state.d.ts
2206
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/input/state.d.ts
2163
2207
  /**
2164
2208
  * Where the pointer is, in the frame's own coordinates.
2165
2209
  *
@@ -2220,7 +2264,7 @@ type InputSnapshot<Action extends string = string> = {
2220
2264
  pointer: PointerState | null;
2221
2265
  };
2222
2266
  //#endregion
2223
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/input/players.d.ts
2267
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/input/players.d.ts
2224
2268
  /**
2225
2269
  * What one player holds: a pad, one part of the keyboard, or the on-screen
2226
2270
  * controls.
@@ -2242,7 +2286,7 @@ type Controller =
2242
2286
  kind: 'touch';
2243
2287
  };
2244
2288
  //#endregion
2245
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/input/pointers.d.ts
2289
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/input/pointers.d.ts
2246
2290
  /**
2247
2291
  * Presses and groups of fingers on an element, recognised and meaning nothing:
2248
2292
  * what a game's tools, or a camera, are built on.
@@ -2427,7 +2471,7 @@ type Pointers = {
2427
2471
  dispose(): void;
2428
2472
  };
2429
2473
  //#endregion
2430
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/input/wheel.d.ts
2474
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/input/wheel.d.ts
2431
2475
  /**
2432
2476
  * One turn of a wheel, swipe of a trackpad, or pinch on one, the same in every
2433
2477
  * browser: `input.wheel`.
@@ -2470,7 +2514,7 @@ type WheelTaker = {
2470
2514
  dispose(): void;
2471
2515
  };
2472
2516
  //#endregion
2473
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/input/camera.d.ts
2517
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/input/camera.d.ts
2474
2518
  /**
2475
2519
  * What the camera should do: one for each move of a finger, a pair of them,
2476
2520
  * a wheel or a glide, whichever it was.
@@ -2615,7 +2659,7 @@ type ViewOptions = {
2615
2659
  height?: number;
2616
2660
  };
2617
2661
  //#endregion
2618
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/input/lock.d.ts
2662
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/input/lock.d.ts
2619
2663
  /**
2620
2664
  * The mouse held to the game: `input.pointerLock`.
2621
2665
  *
@@ -2659,7 +2703,7 @@ type PointerLock = {
2659
2703
  };
2660
2704
  };
2661
2705
  //#endregion
2662
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/input/index.d.ts
2706
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/input/index.d.ts
2663
2707
  /**
2664
2708
  * A binding a game can name on its own, for a prompt about a button that is
2665
2709
  * not one of its actions: `pad.start`, `key.Space`.
@@ -2945,7 +2989,7 @@ type Controls<Layout extends string = string> = {
2945
2989
  readonly available: boolean;
2946
2990
  };
2947
2991
  //#endregion
2948
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/lifecycle.d.ts
2992
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/lifecycle.d.ts
2949
2993
  /** @group Lifecycle */
2950
2994
  type LifecycleListener = (phase: Phase, previous: Phase) => void | Promise<void>;
2951
2995
  /**
@@ -2964,7 +3008,7 @@ type Lifecycle = {
2964
3008
  on(listener: LifecycleListener): Unsubscribe;
2965
3009
  };
2966
3010
  //#endregion
2967
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/loop.d.ts
3011
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/loop.d.ts
2968
3012
  /**
2969
3013
  * The host has let the game run its loop, or stopped it. Return a promise to
2970
3014
  * make the host wait.
@@ -3018,7 +3062,7 @@ type Loop = {
3018
3062
  tick(frame: FrameCallback): Unsubscribe;
3019
3063
  };
3020
3064
  //#endregion
3021
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/panel.d.ts
3065
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/panel.d.ts
3022
3066
  /**
3023
3067
  * The pages of the host's panel a game has a name for, as the keys of this
3024
3068
  * interface.
@@ -3156,7 +3200,7 @@ type Panel = {
3156
3200
  open(page: PageId): void;
3157
3201
  };
3158
3202
  //#endregion
3159
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/pause.d.ts
3203
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/pause.d.ts
3160
3204
  /**
3161
3205
  * The host has asked for a pause, or for the game to carry on.
3162
3206
  *
@@ -3217,7 +3261,7 @@ type Pause = {
3217
3261
  on(listener: PauseListener): Unsubscribe;
3218
3262
  };
3219
3263
  //#endregion
3220
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/resources.d.ts
3264
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/resources.d.ts
3221
3265
  /**
3222
3266
  * The host has let the game hold its resources again, or asked for them back.
3223
3267
  * Return a promise to make the host wait until they have gone, or come back.
@@ -3247,7 +3291,7 @@ type Resources = {
3247
3291
  on(listener: ResourcesListener): Unsubscribe;
3248
3292
  };
3249
3293
  //#endregion
3250
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/save.d.ts
3294
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/save.d.ts
3251
3295
  /**
3252
3296
  * What a game gets back from a read: its own schema number, and its state.
3253
3297
  *
@@ -3422,7 +3466,7 @@ type Save = {
3422
3466
  readonly maxBytes: number;
3423
3467
  };
3424
3468
  //#endregion
3425
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/score.d.ts
3469
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/score.d.ts
3426
3470
  /**
3427
3471
  * Where this player stands on every board, as far as anybody has said.
3428
3472
  *
@@ -3499,7 +3543,7 @@ type Score<Board extends string = string> = {
3499
3543
  }): Promise<ScoreResult>;
3500
3544
  };
3501
3545
  //#endregion
3502
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/share.d.ts
3546
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/share.d.ts
3503
3547
  /**
3504
3548
  * @group Share
3505
3549
  * @category Important
@@ -3583,7 +3627,7 @@ type Share = {
3583
3627
  }): void;
3584
3628
  };
3585
3629
  //#endregion
3586
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/game/src/init.d.ts
3630
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/game/src/init.d.ts
3587
3631
  /**
3588
3632
  * What `init` hands back: every namespace a game has, on one object.
3589
3633
  *
package/manifest.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../../../../../../../tmp/hopple-sdk-types-hbSZtA/manifest/src/manifest.d.ts
1
+ //#region ../../../../../../../tmp/hopple-sdk-types-SHySdg/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.
@@ -894,6 +894,36 @@ type PreviewsDeclaration = {
894
894
  * @category Useful
895
895
  */
896
896
  type GameGenre = 'racing' | 'shooter' | 'platformer' | 'puzzle' | 'arcade' | 'physics' | 'roguelike' | 'strategy' | 'building' | 'idle' | 'sports' | 'horror' | 'stealth' | 'fighting' | 'casual' | 'simulation' | 'adventure' | 'rhythm' | 'rpg' | 'local-multiplayer' | 'online-multiplayer';
897
+ /**
898
+ * A few seconds of real play, which Explore shows in place of the still cover,
899
+ * played muted, over and over. See `GameManifest.clip`.
900
+ *
901
+ * @group Manifest
902
+ * @category Useful
903
+ */
904
+ type ClipDeclaration = {
905
+ /**
906
+ * The clip: the path of an MP4 file beside the config. H.264 with no sound
907
+ * track, 2 to 15 seconds long, its longest side at most 1280 and its
908
+ * shortest at most 720, saved with "fast start", and at most
909
+ * `CLIP_MAX_BYTES`. `clipProblems` says what is wrong with one.
910
+ *
911
+ * @pattern ^(?!/)(?!.*\.\.)[A-Za-z0-9._/-]+\.mp4$
912
+ * @maxLength 200
913
+ * @errorMessage must be the path of an .mp4 file beside the config, like "clip.mp4"
914
+ */
915
+ video: string;
916
+ /**
917
+ * The clip's first frame, as a PNG, JPEG or WebP file beside the config,
918
+ * shown until the clip plays and wherever it cannot. At most
919
+ * `POSTER_MAX_BYTES`.
920
+ *
921
+ * @pattern ^(?!/)(?!.*\.\.)[A-Za-z0-9._/-]+\.(?:png|jpe?g|webp)$
922
+ * @maxLength 200
923
+ * @errorMessage must be the path of a .png, .jpg or .webp file beside the config, like "clip.png"
924
+ */
925
+ poster: string;
926
+ };
897
927
  /**
898
928
  * Publishing refuses a key the manifest's API does not know; a host reading a
899
929
  * published one ignores it (see `validateManifest`).
@@ -981,20 +1011,34 @@ type GameManifest = {
981
1011
  */
982
1012
  genres?: GameGenre[];
983
1013
  /**
984
- * A picture of the game, shown wherever a link to it is pasted — a chat, a
985
- * post, a message.
1014
+ * A picture of the game: its cover on Explore's shelves and in a player's
1015
+ * inbox, and what a link to it shows wherever it is pasted — a chat, a post,
1016
+ * a message.
986
1017
  *
987
1018
  * The path of a PNG, JPEG or WebP file, relative to the config the manifest
988
1019
  * is in, and at most `COVER_MAX_BYTES`. `hopple publish` uploads it beside
989
- * the game. 1200 × 630 is the shape every platform shows whole; without one,
990
- * a link shows Hopple's own card.
1020
+ * the game, and a server takes no upload without one: only a game that is
1021
+ * never uploaded leaves it out. 1200 × 630 is the shape every platform shows
1022
+ * whole; `hopple smoke --cover` makes one from the game itself.
991
1023
  *
992
- * @category Useful
1024
+ * @category Important
993
1025
  * @pattern ^(?!/)(?!.*\.\.)[A-Za-z0-9._/-]+\.(?:png|jpe?g|webp)$
994
1026
  * @maxLength 200
995
1027
  * @errorMessage must be the path of a .png, .jpg or .webp file beside the config, like "cover.png"
996
1028
  */
997
1029
  cover?: string;
1030
+ /**
1031
+ * A few seconds of real play, which Explore shows in place of the still
1032
+ * `cover`, played muted. `poster` is the clip's first frame, shown until it
1033
+ * plays. The clip is an H.264 MP4 with no sound track, 2 to 15 seconds long,
1034
+ * no larger than 1280 × 720 either way up, saved with "fast start", and at
1035
+ * most `CLIP_MAX_BYTES`; the poster at most `POSTER_MAX_BYTES`. Both are
1036
+ * paths beside the config, and `hopple publish` uploads them with the game.
1037
+ * See `ClipDeclaration`.
1038
+ *
1039
+ * @category Useful
1040
+ */
1041
+ clip?: ClipDeclaration;
998
1042
  /** @category Important */
999
1043
  orientation: Orientation;
1000
1044
  /**
package/manifest.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @hopple/sdk 1.0.0-next.12 | Copyright (c) 2026 The Hopple authors | MIT License */
1
+ /*! @hopple/sdk 1.0.0-next.13 | Copyright (c) 2026 The Hopple authors | MIT License */
2
2
  //#region ../manifest/src/manifest.schema.ts
3
3
  const GameManifest = {
4
4
  "type": "object",
@@ -46,6 +46,7 @@ const GameManifest = {
46
46
  "maxLength": 200,
47
47
  "errorMessage": "must be the path of a .png, .jpg or .webp file beside the config, like \"cover.png\""
48
48
  },
49
+ "clip": { "$ref": "#/definitions/ClipDeclaration" },
49
50
  "orientation": { "$ref": "#/definitions/Orientation" },
50
51
  "layout": { "$ref": "#/definitions/LayoutDeclaration" },
51
52
  "analytics": { "$ref": "#/definitions/AnalyticsDeclaration" },
@@ -98,6 +99,25 @@ const GameGenre = {
98
99
  "online-multiplayer"
99
100
  ]
100
101
  };
102
+ const ClipDeclaration = {
103
+ "type": "object",
104
+ "properties": {
105
+ "video": {
106
+ "type": "string",
107
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.mp4$",
108
+ "maxLength": 200,
109
+ "errorMessage": "must be the path of an .mp4 file beside the config, like \"clip.mp4\""
110
+ },
111
+ "poster": {
112
+ "type": "string",
113
+ "pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.(?:png|jpe?g|webp)$",
114
+ "maxLength": 200,
115
+ "errorMessage": "must be the path of a .png, .jpg or .webp file beside the config, like \"clip.png\""
116
+ }
117
+ },
118
+ "required": ["video", "poster"],
119
+ "additionalProperties": false
120
+ };
101
121
  const Orientation = {
102
122
  "type": "string",
103
123
  "enum": [
@@ -461,6 +481,7 @@ const definitions = {
461
481
  GameManifest,
462
482
  ApiVersion,
463
483
  GameGenre,
484
+ ClipDeclaration,
464
485
  Orientation,
465
486
  LayoutDeclaration,
466
487
  InsetMode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hopple/sdk",
3
- "version": "1.0.0-next.12",
3
+ "version": "1.0.0-next.13",
4
4
  "description": "The Hopple SDK: what a game imports to talk to the host that runs it, forwarded to the runtime its page loads.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://hopple.app",