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

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.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 | 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-BSfTrJ/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.
@@ -445,7 +487,7 @@ type InputDeviceSet = InputDevice[];
445
487
  * doubled. A thumbstick is not one of these; see `InputStick`.
446
488
  *
447
489
  * @group Input
448
- * @pattern ^(?:key\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|pointer\.(?:primary|secondary)|mouse\.(?:left|middle|right)|touch\.tap|controls\.(?=.{1,48}$)[a-z0-9]+(?:[_-][a-z0-9]+)*|pad\.(?:north|south|east|west|start|select)|pad\.(?:left|right)\.(?:north|south|east|west|bumper|trigger))$
490
+ * @pattern ^(?:key\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|pointer\.(?:primary|secondary)|mouse\.(?:left|middle|right|wheelup|wheeldown)|touch\.tap|controls\.(?=.{1,48}$)[a-z0-9]+(?:[_-][a-z0-9]+)*|pad\.(?:north|south|east|west|start|select)|pad\.(?:left|right)\.(?:north|south|east|west|bumper|trigger))$
449
491
  * @errorMessage is not a binding this SDK knows (a thumbstick is read rather than bound, and Escape is the host's key)
450
492
  */
451
493
  type InputBinding = string;
@@ -521,7 +563,7 @@ type PlayerBinding = string;
521
563
  * mouse button.
522
564
  *
523
565
  * @group Input
524
- * @pattern ^(?:key\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|mouse\.(?:left|middle|right))$
566
+ * @pattern ^(?:key\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|mouse\.(?:left|middle|right|wheelup|wheeldown))$
525
567
  * @errorMessage must be a key or a mouse button (and Escape is the host's key)
526
568
  */
527
569
  type KeyboardBinding = string;
@@ -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.-]+)?$
package/manifest.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.9 | Copyright (c) 2026 The Hopple authors | MIT License */
2
2
  //#region ../manifest/src/manifest.schema.ts
3
3
  const GameManifest = {
4
4
  "type": "object",
@@ -186,7 +186,12 @@ const ScoreBoard = {
186
186
  "errorMessage": "must not be blank"
187
187
  },
188
188
  "direction": { "$ref": "#/definitions/ScoreDirection" },
189
- "format": { "$ref": "#/definitions/ScoreFormat" }
189
+ "format": { "$ref": "#/definitions/ScoreFormat" },
190
+ "version": {
191
+ "type": "integer",
192
+ "minimum": 1,
193
+ "maximum": 1e3
194
+ }
190
195
  },
191
196
  "required": ["title", "direction"],
192
197
  "additionalProperties": false
@@ -241,7 +246,12 @@ const Achievement = {
241
246
  "errorMessage": "must not be blank"
242
247
  },
243
248
  "secret": { "type": "boolean" },
244
- "progress": { "$ref": "#/definitions/AchievementProgress" }
249
+ "progress": { "$ref": "#/definitions/AchievementProgress" },
250
+ "version": {
251
+ "type": "integer",
252
+ "minimum": 1,
253
+ "maximum": 1e3
254
+ }
245
255
  },
246
256
  "required": ["title"],
247
257
  "additionalProperties": false
@@ -365,7 +375,7 @@ const InputBindings = {
365
375
  };
366
376
  const InputBinding = {
367
377
  "type": "string",
368
- "pattern": "^(?:key\\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|pointer\\.(?:primary|secondary)|mouse\\.(?:left|middle|right)|touch\\.tap|controls\\.(?=.{1,48}$)[a-z0-9]+(?:[_-][a-z0-9]+)*|pad\\.(?:north|south|east|west|start|select)|pad\\.(?:left|right)\\.(?:north|south|east|west|bumper|trigger))$",
378
+ "pattern": "^(?:key\\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|pointer\\.(?:primary|secondary)|mouse\\.(?:left|middle|right|wheelup|wheeldown)|touch\\.tap|controls\\.(?=.{1,48}$)[a-z0-9]+(?:[_-][a-z0-9]+)*|pad\\.(?:north|south|east|west|start|select)|pad\\.(?:left|right)\\.(?:north|south|east|west|bumper|trigger))$",
369
379
  "errorMessage": "is not a binding this SDK knows (a thumbstick is read rather than bound, and Escape is the host's key)"
370
380
  };
371
381
  const ControlLayout = {
@@ -562,7 +572,7 @@ const definitions = {
562
572
  },
563
573
  KeyboardBinding: {
564
574
  "type": "string",
565
- "pattern": "^(?:key\\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|mouse\\.(?:left|middle|right))$",
575
+ "pattern": "^(?:key\\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|mouse\\.(?:left|middle|right|wheelup|wheeldown))$",
566
576
  "errorMessage": "must be a key or a mouse button (and Escape is the host's key)"
567
577
  },
568
578
  PlayersDeclaration: {
package/manifest.md CHANGED
@@ -55,15 +55,17 @@ same function, so they agree on whether a manifest is valid.
55
55
 
56
56
  ### In an editor
57
57
 
58
- `@hopple/sdk` ships the hopple config's JSON Schema, the manifest's included,
59
- as `config.schema.json`, for completion, hover docs and errors as you type. A
60
- JSON config can point at it:
58
+ The `hopple` CLI ships the hopple config's JSON Schema, the manifest's
59
+ included, as `config.schema.json`, for completion, hover docs and errors as
60
+ you type. A JSON config in a project with `hopple` installed can point at it:
61
61
 
62
62
  ```json
63
- { "$schema": "./node_modules/@hopple/sdk/config.schema.json", "page": "dist/index.html", "manifest": { ... } }
63
+ { "$schema": "./node_modules/hopple/config.schema.json", "page": "dist/index.html", "manifest": { ... } }
64
64
  ```
65
65
 
66
- A TypeScript config gets the same checks from its type, `HoppleConfig`.
66
+ A TypeScript config gets the same checks from its type,
67
+ `import type { HoppleConfig } from 'hopple'`, whose manifest is the type of
68
+ the installed `@hopple/sdk`.
67
69
 
68
70
  A few rules relate one part of a manifest to another, which JSON Schema can't
69
71
  express: buttons must name declared actions, a control's `as` must name its
@@ -142,6 +144,7 @@ A game without this key can still use `sdk.save`.
142
144
  | `title` | yes | What players see. At most 40 characters. |
143
145
  | `direction` | yes | `desc`: higher is better. `asc`: lower is better (times, strokes). |
144
146
  | `format` | no | `integer` (default), `decimal`, or `duration-ms` (so `224310` shows as `3:44.31`). |
147
+ | `version` | no | 1 by default. Raising it starts the board again; see [Changing the manifest in a new version](#changing-the-manifest-in-a-new-version). |
145
148
 
146
149
  `boards` is a map even for one board, so a game can add a second one later
147
150
  without changing the meaning of the first. At most 16 boards.
@@ -180,6 +183,9 @@ There are two kinds:
180
183
  `secret: true` hides the title and detail from players until it is earned.
181
184
  The manifest is public, so this only stops spoilers.
182
185
 
186
+ `version` is 1 by default. Raising it starts the achievement again for
187
+ everybody; see [Changing the manifest in a new version](#changing-the-manifest-in-a-new-version).
188
+
183
189
  Limits: 128 achievements, titles up to 40 characters, details up to 120, icons
184
190
  up to 8 characters, 8 tiers.
185
191
 
@@ -354,17 +360,27 @@ approval and links, and an upload declaring it is refused.
354
360
 
355
361
  ## Changing the manifest in a new version
356
362
 
357
- The server refuses a new version whose manifest would corrupt data players
358
- already have:
359
-
360
- - **A score board that has scores can't be removed or change direction.**
361
- Removing it would orphan the scores (and renaming a board looks like removing
362
- it); flipping `direction` would turn the whole board upside down. An admin
363
- can clear the board first if that is really what you want.
364
- - **A published achievement can't be changed.** Its title, detail, icon, secret
365
- flag, kind, target, direction, format and tiers are fixed, because players already
366
- hold it. You can add new achievements, and you can stop declaring old ones
367
- (what players earned is kept).
363
+ Every board and every achievement has a `version`, 1 when left out. The
364
+ server refuses a new version of the game whose manifest would change what data
365
+ players already have means, and the way out is always to raise that board's or
366
+ achievement's `version`: it starts again from nobody under the same id, and
367
+ what was stored under the version before is kept but no longer counted.
368
+
369
+ - **Boards and achievements can be added and dropped freely.** What players
370
+ stored under one that is dropped is kept, and comes back if it is declared
371
+ again.
372
+ - **A board's title can change, and so can its format between `integer` and
373
+ `decimal`.** Once it has scores, flipping its `direction` or moving its
374
+ format to or from `duration-ms` needs a new `version`.
375
+ - **An achievement's title, detail, icon and secret flag can change, and so can
376
+ its target; tiers can be added after the last one.** Progress is kept as the
377
+ number reported, so a player at 12 of 12 is at 12 of 25 when the target is
378
+ raised, and keeps the unlock; when it is lowered past them, they have it.
379
+ Turning a flag into a measured achievement or back, flipping `direction`,
380
+ moving its format to or from `duration-ms`, or moving or dropping a tier it
381
+ already has needs a new `version`, whether or not anybody holds it yet.
382
+ - **A version declared again is held to what it was**, since what was stored
383
+ under it comes back.
368
384
 
369
385
  Input action ids and audio channel ids may change freely. A player's remapped
370
386
  keys or volume settings for an id that no longer exists are simply dropped.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hopple/sdk",
3
- "version": "1.0.0-next.4",
3
+ "version": "1.0.0-next.9",
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",
@@ -29,7 +29,6 @@
29
29
  "types": "./manifest.d.ts",
30
30
  "default": "./manifest.js"
31
31
  },
32
- "./config.schema.json": "./config.schema.json",
33
32
  "./package.json": "./package.json"
34
33
  },
35
34
  "main": "./index.js",