@hopple/sdk 1.0.0-next.1 → 1.0.0-next.4
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/README.md +38 -2
- package/config.schema.json +50 -0
- package/index.d.ts +96 -28
- package/index.js +1 -1
- package/manifest.d.ts +53 -1
- package/manifest.js +47 -2
- package/manifest.md +28 -1
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -673,8 +673,44 @@ sdk.input.controls.showing; // whether controls are actually visible
|
|
|
673
673
|
- Hiding them only stops drawing them; keys and gamepads keep working.
|
|
674
674
|
- While shown, they appear in `sdk.layout.obstructions` as reserved
|
|
675
675
|
rectangles, so code that avoids the host's buttons avoids them too.
|
|
676
|
-
- They are drawn in a shadow root inside your frame, so
|
|
677
|
-
|
|
676
|
+
- They are drawn in a shadow root inside your frame, so they don't appear in
|
|
677
|
+
your DOM and your CSS only reaches them where it is meant to: through
|
|
678
|
+
`hopple-controls::part(…)` and the `--hopple-pad-*` properties (see
|
|
679
|
+
[Styling them](#styling-the-on-screen-controls)).
|
|
680
|
+
|
|
681
|
+
### Styling the on-screen controls
|
|
682
|
+
|
|
683
|
+
```css
|
|
684
|
+
hopple-controls {
|
|
685
|
+
--hopple-pad-tint: #2b1d0ecc; /* a button's fill */
|
|
686
|
+
--hopple-pad-edge: #f5c542; /* rims, the knob */
|
|
687
|
+
--hopple-pad-ink: #fff8e1; /* labels and icons */
|
|
688
|
+
--hopple-pad-radius: 14px;
|
|
689
|
+
--hopple-pad-font: 700 16px/1 'My Font', sans-serif, hopple-keys;
|
|
690
|
+
}
|
|
691
|
+
hopple-controls::part(face pressed) { translate: 0 2px; }
|
|
692
|
+
hopple-controls::part(face action-jump) { background: url(ui/jump.png) center / 70% no-repeat; }
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
```ts
|
|
696
|
+
sdk.input.icon('jump', 'M12 3 4 13h5v8h6v-8h5z'); // a filled path in a 24×24 box, or null
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
- Parts: `face`, `label` and `icon` on a button; `group` behind a group of
|
|
700
|
+
buttons; `well`, `knob`, `groove` and `arrow` on a stick. Each also carries
|
|
701
|
+
`action-<id>` and `control-<id>`, and the states `pressed`, `held`,
|
|
702
|
+
`floating` and `resting`.
|
|
703
|
+
- `<hopple-controls>` has `layout`, `sticks` and `look` attributes, and a
|
|
704
|
+
stick has `--hopple-stick-x` and `--hopple-stick-y` (−1 to 1) for a game to
|
|
705
|
+
read.
|
|
706
|
+
- Sizes (`--hopple-pad-size`, `-stick`, `-track`, `-gap`, `-inset`) are held
|
|
707
|
+
between a size a thumb can press and one that leaves the game room. How big
|
|
708
|
+
one control is next to the others is its `scale` in the manifest. What a
|
|
709
|
+
part draws never changes where a touch lands.
|
|
710
|
+
- A player can turn a game's look off, for every game or for one, and gets the
|
|
711
|
+
plain controls back: test that they still read well over the game.
|
|
712
|
+
- Under `hopple dev`, a rule that names a part or a property the controls don't
|
|
713
|
+
have is reported to `onError`.
|
|
678
714
|
|
|
679
715
|
### Several players on one device
|
|
680
716
|
|
package/config.schema.json
CHANGED
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
"maxLength": 64,
|
|
64
64
|
"errorMessage": "must be a handle: lowercase letters, digits and hyphens"
|
|
65
65
|
},
|
|
66
|
+
"publishable": {
|
|
67
|
+
"type": "boolean",
|
|
68
|
+
"description": "`false` for a game that is never to be uploaded: an example, a test, a game kept to one machine. `hopple dev` and `hopple check` still run it; `hopple upload` and `hopple publish` refuse it, and so does a server it is sent to another way. Absent, it may be published.",
|
|
69
|
+
"markdownDescription": "`false` for a game that is never to be uploaded: an example, a test, a\ngame kept to one machine. `hopple dev` and `hopple check` still run it;\n`hopple upload` and `hopple publish` refuse it, and so does a server it\nis sent to another way. Absent, it may be published."
|
|
70
|
+
},
|
|
66
71
|
"version": {
|
|
67
72
|
"type": "string",
|
|
68
73
|
"description": "The game's own version, `MAJOR.MINOR.PATCH`. Ours to read, never to parse for meaning.",
|
|
@@ -84,6 +89,16 @@
|
|
|
84
89
|
"maxLength": 500,
|
|
85
90
|
"errorMessage": "must not be blank"
|
|
86
91
|
},
|
|
92
|
+
"genres": {
|
|
93
|
+
"type": "array",
|
|
94
|
+
"items": {
|
|
95
|
+
"$ref": "#/definitions/GameGenre"
|
|
96
|
+
},
|
|
97
|
+
"description": "What kind of game it is, the main one first. Read by whoever files it in a catalogue, which never runs it. At most `GENRE_MAX`, not counting `local-multiplayer` and `online-multiplayer`: a game that is every kind tells a player nothing. The publisher's claim, taken at its word, except for those two. See `GameGenre`.",
|
|
98
|
+
"markdownDescription": "What kind of game it is, the main one first. Read by whoever files it in a\ncatalogue, which never runs it. At most `GENRE_MAX`, not counting\n`local-multiplayer` and `online-multiplayer`: a game that is every kind\ntells a player nothing. The publisher's claim, taken at its word, except\nfor those two. See `GameGenre`.",
|
|
99
|
+
"minItems": 1,
|
|
100
|
+
"maxItems": 5
|
|
101
|
+
},
|
|
87
102
|
"cover": {
|
|
88
103
|
"type": "string",
|
|
89
104
|
"description": "A picture of the game, shown wherever a link to it is pasted — a chat, a post, a message.\n\nThe path of a PNG, JPEG or WebP file, relative to the config the manifest is in, and at most `COVER_MAX_BYTES`. `hopple publish` uploads it beside the game. 1200 × 630 is the shape every platform shows whole; without one, a link shows Hopple's own card.",
|
|
@@ -165,6 +180,34 @@
|
|
|
165
180
|
"pattern": "^(?:(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)|next)$",
|
|
166
181
|
"errorMessage": "must be the API the game is written against, like \"1.0\", or \"next\""
|
|
167
182
|
},
|
|
183
|
+
"GameGenre": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"enum": [
|
|
186
|
+
"racing",
|
|
187
|
+
"shooter",
|
|
188
|
+
"platformer",
|
|
189
|
+
"puzzle",
|
|
190
|
+
"arcade",
|
|
191
|
+
"physics",
|
|
192
|
+
"roguelike",
|
|
193
|
+
"strategy",
|
|
194
|
+
"building",
|
|
195
|
+
"idle",
|
|
196
|
+
"sports",
|
|
197
|
+
"horror",
|
|
198
|
+
"stealth",
|
|
199
|
+
"fighting",
|
|
200
|
+
"casual",
|
|
201
|
+
"simulation",
|
|
202
|
+
"adventure",
|
|
203
|
+
"rhythm",
|
|
204
|
+
"rpg",
|
|
205
|
+
"local-multiplayer",
|
|
206
|
+
"online-multiplayer"
|
|
207
|
+
],
|
|
208
|
+
"description": "What kind of game this is, as one word from a list every game shares.\n\nA closed list, because a client files games under these and has to name each one in front of a player: left to free text, one kind of game becomes three spellings of it with a game each. The list only grows, since a published manifest has to stay valid. Whoever reads one may be older than the game, and ignores a word it does not know.\n\n`local-multiplayer` and `online-multiplayer` say how a game is played rather than what it is, and only count where the manifest bears them out: see `declaredGenres`.",
|
|
209
|
+
"markdownDescription": "What kind of game this is, as one word from a list every game shares.\n\nA closed list, because a client files games under these and has to name each\none in front of a player: left to free text, one kind of game becomes three\nspellings of it with a game each. The list only grows, since a published\nmanifest has to stay valid. Whoever reads one may be older than the game, and\nignores a word it does not know.\n\n`local-multiplayer` and `online-multiplayer` say how a game is played rather\nthan what it is, and only count where the manifest bears them out: see\n`declaredGenres`."
|
|
210
|
+
},
|
|
168
211
|
"Orientation": {
|
|
169
212
|
"type": "string",
|
|
170
213
|
"enum": [
|
|
@@ -678,6 +721,13 @@
|
|
|
678
721
|
"$ref": "#/definitions/ControlAxis",
|
|
679
722
|
"description": "`thumbstick` only. See `ControlAxis`. Absent, the stick moves both ways.",
|
|
680
723
|
"markdownDescription": "`thumbstick` only. See `ControlAxis`. Absent, the stick moves both ways."
|
|
724
|
+
},
|
|
725
|
+
"scale": {
|
|
726
|
+
"type": "number",
|
|
727
|
+
"description": "How big this control is next to the layout's others: `1.25` for a jump button a thumb lands on without looking, `0.8` for one pressed now and then. Absent, 1.\n\nA size in the layout rather than in the game's stylesheet, because whether the layout fits on a small screen is worked out from it before the controls are drawn. A game's stylesheet changes how they look, never how much of the screen they answer in.",
|
|
728
|
+
"markdownDescription": "How big this control is next to the layout's others: `1.25` for a jump\nbutton a thumb lands on without looking, `0.8` for one pressed now and then.\nAbsent, 1.\n\nA size in the layout rather than in the game's stylesheet, because whether\nthe layout fits on a small screen is worked out from it before the\ncontrols are drawn. A game's stylesheet changes how they look, never how\nmuch of the screen they answer in.",
|
|
729
|
+
"minimum": 0.75,
|
|
730
|
+
"maximum": 1.5
|
|
681
731
|
}
|
|
682
732
|
},
|
|
683
733
|
"required": [
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/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-
|
|
32
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/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-
|
|
118
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/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-
|
|
178
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/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-
|
|
236
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/save.d.ts
|
|
237
237
|
/**
|
|
238
238
|
* The one thing a game may keep.
|
|
239
239
|
*
|
|
@@ -284,7 +284,7 @@ type SaveStored = {
|
|
|
284
284
|
bytes: number;
|
|
285
285
|
};
|
|
286
286
|
//#endregion
|
|
287
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
287
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/score.d.ts
|
|
288
288
|
/**
|
|
289
289
|
* What the host says came of it.
|
|
290
290
|
*
|
|
@@ -322,7 +322,7 @@ type ScoreStanding = {
|
|
|
322
322
|
of?: number;
|
|
323
323
|
};
|
|
324
324
|
//#endregion
|
|
325
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
325
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/share.d.ts
|
|
326
326
|
/**
|
|
327
327
|
* A link to this game, offered to the player by the host.
|
|
328
328
|
*
|
|
@@ -421,7 +421,7 @@ type ShareMeta = {
|
|
|
421
421
|
[key: string]: ShareMetaValue;
|
|
422
422
|
};
|
|
423
423
|
//#endregion
|
|
424
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
424
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/methods.d.ts
|
|
425
425
|
/**
|
|
426
426
|
* How far a game has got with its own preparation.
|
|
427
427
|
*
|
|
@@ -601,7 +601,7 @@ type DeviceInfo = {
|
|
|
601
601
|
tier: DeviceTier;
|
|
602
602
|
};
|
|
603
603
|
//#endregion
|
|
604
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
604
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/listeners.d.ts
|
|
605
605
|
/**
|
|
606
606
|
* The one way anything here is listened to: `on(listener)` hands back the
|
|
607
607
|
* function that stops it.
|
|
@@ -630,7 +630,7 @@ type DeviceInfo = {
|
|
|
630
630
|
*/
|
|
631
631
|
type Unsubscribe = () => void;
|
|
632
632
|
//#endregion
|
|
633
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
633
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/manifest/src/manifest.d.ts
|
|
634
634
|
/**
|
|
635
635
|
* The API a game is written against: `M.m`, a major and the least minor it
|
|
636
636
|
* needs, or `next`, the pre-releases of the next stable release.
|
|
@@ -1309,6 +1309,20 @@ type ControlSlot = {
|
|
|
1309
1309
|
as?: InputStick | Record<string, InputBinding$1>;
|
|
1310
1310
|
/** `thumbstick` only. See `ControlAxis`. Absent, the stick moves both ways. */
|
|
1311
1311
|
axis?: ControlAxis;
|
|
1312
|
+
/**
|
|
1313
|
+
* How big this control is next to the layout's others: `1.25` for a jump
|
|
1314
|
+
* button a thumb lands on without looking, `0.8` for one pressed now and then.
|
|
1315
|
+
* Absent, 1.
|
|
1316
|
+
*
|
|
1317
|
+
* A size in the layout rather than in the game's stylesheet, because whether
|
|
1318
|
+
* the layout fits on a small screen is worked out from it before the
|
|
1319
|
+
* controls are drawn. A game's stylesheet changes how they look, never how
|
|
1320
|
+
* much of the screen they answer in.
|
|
1321
|
+
*
|
|
1322
|
+
* @minimum 0.75
|
|
1323
|
+
* @maximum 1.5
|
|
1324
|
+
*/
|
|
1325
|
+
scale?: number;
|
|
1312
1326
|
};
|
|
1313
1327
|
/**
|
|
1314
1328
|
* The one direction an on-screen thumbstick moves in: `x` across, `y` up and
|
|
@@ -1459,6 +1473,23 @@ type PreviewsDeclaration = {
|
|
|
1459
1473
|
*/
|
|
1460
1474
|
network?: PreviewRevision;
|
|
1461
1475
|
};
|
|
1476
|
+
/**
|
|
1477
|
+
* What kind of game this is, as one word from a list every game shares.
|
|
1478
|
+
*
|
|
1479
|
+
* A closed list, because a client files games under these and has to name each
|
|
1480
|
+
* one in front of a player: left to free text, one kind of game becomes three
|
|
1481
|
+
* spellings of it with a game each. The list only grows, since a published
|
|
1482
|
+
* manifest has to stay valid. Whoever reads one may be older than the game, and
|
|
1483
|
+
* ignores a word it does not know.
|
|
1484
|
+
*
|
|
1485
|
+
* `local-multiplayer` and `online-multiplayer` say how a game is played rather
|
|
1486
|
+
* than what it is, and only count where the manifest bears them out: see
|
|
1487
|
+
* `declaredGenres`.
|
|
1488
|
+
*
|
|
1489
|
+
* @group Manifest
|
|
1490
|
+
* @category Useful
|
|
1491
|
+
*/
|
|
1492
|
+
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';
|
|
1462
1493
|
/**
|
|
1463
1494
|
* Publishing refuses a key the manifest's API does not know; a host reading a
|
|
1464
1495
|
* published one ignores it (see `validateManifest`).
|
|
@@ -1498,6 +1529,15 @@ type GameManifest = {
|
|
|
1498
1529
|
* @errorMessage must be a handle: lowercase letters, digits and hyphens
|
|
1499
1530
|
*/
|
|
1500
1531
|
owner?: string;
|
|
1532
|
+
/**
|
|
1533
|
+
* `false` for a game that is never to be uploaded: an example, a test, a
|
|
1534
|
+
* game kept to one machine. `hopple dev` and `hopple check` still run it;
|
|
1535
|
+
* `hopple upload` and `hopple publish` refuse it, and so does a server it
|
|
1536
|
+
* is sent to another way. Absent, it may be published.
|
|
1537
|
+
*
|
|
1538
|
+
* @category Useful
|
|
1539
|
+
*/
|
|
1540
|
+
publishable?: boolean;
|
|
1501
1541
|
/**
|
|
1502
1542
|
* The game's own version, `MAJOR.MINOR.PATCH`. Ours to read, never to parse for meaning.
|
|
1503
1543
|
*
|
|
@@ -1522,6 +1562,18 @@ type GameManifest = {
|
|
|
1522
1562
|
* @errorMessage must not be blank
|
|
1523
1563
|
*/
|
|
1524
1564
|
description?: string;
|
|
1565
|
+
/**
|
|
1566
|
+
* What kind of game it is, the main one first. Read by whoever files it in a
|
|
1567
|
+
* catalogue, which never runs it. At most `GENRE_MAX`, not counting
|
|
1568
|
+
* `local-multiplayer` and `online-multiplayer`: a game that is every kind
|
|
1569
|
+
* tells a player nothing. The publisher's claim, taken at its word, except
|
|
1570
|
+
* for those two. See `GameGenre`.
|
|
1571
|
+
*
|
|
1572
|
+
* @category Useful
|
|
1573
|
+
* @minItems 1
|
|
1574
|
+
* @maxItems 5
|
|
1575
|
+
*/
|
|
1576
|
+
genres?: GameGenre[];
|
|
1525
1577
|
/**
|
|
1526
1578
|
* A picture of the game, shown wherever a link to it is pasted — a chat, a
|
|
1527
1579
|
* post, a message.
|
|
@@ -1654,7 +1706,7 @@ type ProjectManifest = Omit<GameManifest, 'api'> & {
|
|
|
1654
1706
|
api?: ApiVersion;
|
|
1655
1707
|
};
|
|
1656
1708
|
//#endregion
|
|
1657
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1709
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/achievements.d.ts
|
|
1658
1710
|
/**
|
|
1659
1711
|
* Where this player stands on one achievement.
|
|
1660
1712
|
*
|
|
@@ -1752,7 +1804,7 @@ type Achievements<Id extends string = string, Measured extends string = Id> = {
|
|
|
1752
1804
|
progress(id: Measured, value: number): void;
|
|
1753
1805
|
};
|
|
1754
1806
|
//#endregion
|
|
1755
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1807
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/analytics.d.ts
|
|
1756
1808
|
/**
|
|
1757
1809
|
* @group Analytics
|
|
1758
1810
|
* @category Important
|
|
@@ -1769,7 +1821,7 @@ type Analytics<Events extends object = Record<string, AnalyticsProps>> = {
|
|
|
1769
1821
|
track<Event extends keyof Events & string>(name: Event, props?: Events[Event]): void;
|
|
1770
1822
|
};
|
|
1771
1823
|
//#endregion
|
|
1772
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1824
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/audio.d.ts
|
|
1773
1825
|
/**
|
|
1774
1826
|
* Something about the game's sound changed: the host's order, the player's
|
|
1775
1827
|
* mute, a fader. Read what you need — `volume(channel)`, `allowed` — and apply
|
|
@@ -1860,7 +1912,7 @@ type Audio<Channel extends string = string> = {
|
|
|
1860
1912
|
media(element: HTMLMediaElement, channel?: Channel): Unsubscribe;
|
|
1861
1913
|
};
|
|
1862
1914
|
//#endregion
|
|
1863
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1915
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/info.d.ts
|
|
1864
1916
|
/**
|
|
1865
1917
|
* What the host said, frozen.
|
|
1866
1918
|
*
|
|
@@ -1878,7 +1930,7 @@ type Info = Readonly<{
|
|
|
1878
1930
|
device: Readonly<DeviceInfo>;
|
|
1879
1931
|
}>;
|
|
1880
1932
|
//#endregion
|
|
1881
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1933
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/layout.d.ts
|
|
1882
1934
|
/**
|
|
1883
1935
|
* How much of each edge belongs to the host. CSS pixels.
|
|
1884
1936
|
*
|
|
@@ -1988,7 +2040,7 @@ type InsetPolicy = {
|
|
|
1988
2040
|
*/
|
|
1989
2041
|
declare function insetsFrom(obstructions: readonly Obstruction[], frame: Size, policy?: InsetPolicy): Insets;
|
|
1990
2042
|
//#endregion
|
|
1991
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2043
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/names.d.ts
|
|
1992
2044
|
/**
|
|
1993
2045
|
* The keys of a declared map, or `string` where the map is not a literal one —
|
|
1994
2046
|
* which is what a manifest typed as the wide `GameManifest` gives — and
|
|
@@ -2108,7 +2160,7 @@ type EventProps<M> = M extends {
|
|
|
2108
2160
|
*/
|
|
2109
2161
|
type StickName = 'pad.left.stick' | 'pad.right.stick' | 'pad.stick' | `controls.${string}`;
|
|
2110
2162
|
//#endregion
|
|
2111
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2163
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/state.d.ts
|
|
2112
2164
|
/**
|
|
2113
2165
|
* Where the pointer is, in the frame's own coordinates.
|
|
2114
2166
|
*
|
|
@@ -2169,7 +2221,7 @@ type InputSnapshot<Action extends string = string> = {
|
|
|
2169
2221
|
pointer: PointerState | null;
|
|
2170
2222
|
};
|
|
2171
2223
|
//#endregion
|
|
2172
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2224
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/players.d.ts
|
|
2173
2225
|
/**
|
|
2174
2226
|
* What one player holds: a pad, one part of the keyboard, or the on-screen
|
|
2175
2227
|
* controls.
|
|
@@ -2191,7 +2243,7 @@ type Controller =
|
|
|
2191
2243
|
kind: 'touch';
|
|
2192
2244
|
};
|
|
2193
2245
|
//#endregion
|
|
2194
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2246
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/index.d.ts
|
|
2195
2247
|
/**
|
|
2196
2248
|
* A binding a game can name on its own, for a prompt about a button that is
|
|
2197
2249
|
* not one of its actions: `pad.start`, `key.Space`.
|
|
@@ -2301,6 +2353,22 @@ type Input<Action extends string = string, Layout extends string = string, Playe
|
|
|
2301
2353
|
describe(name: Action | ButtonName): string;
|
|
2302
2354
|
/** The font family `glyph` draws in: name it, last, wherever a glyph is printed. */
|
|
2303
2355
|
readonly font: string;
|
|
2356
|
+
/**
|
|
2357
|
+
* Draw an icon on an action's on-screen button instead of its label, or
|
|
2358
|
+
* `null` to go back to the label.
|
|
2359
|
+
*
|
|
2360
|
+
* hopple.input.icon('jump', 'M12 3 4 13h5v8h6v-8h5z');
|
|
2361
|
+
*
|
|
2362
|
+
* One SVG path, filled in the buttons' ink, in a 24 by 24 box: what icon
|
|
2363
|
+
* sets draw filled icons in. The label stays the button's name for a screen
|
|
2364
|
+
* reader. `glyph(action)` on a touch screen draws the same icon, so a prompt
|
|
2365
|
+
* matches the button. A path that cannot be read, or that leaves the box, is
|
|
2366
|
+
* reported to `onError` and the label is drawn instead.
|
|
2367
|
+
*
|
|
2368
|
+
* How the controls look otherwise is the game's stylesheet's: see the
|
|
2369
|
+
* skill's input guide.
|
|
2370
|
+
*/
|
|
2371
|
+
icon(action: Action, path: string | null): void;
|
|
2304
2372
|
/**
|
|
2305
2373
|
* Stop reading input and let go of everything held.
|
|
2306
2374
|
*
|
|
@@ -2412,7 +2480,7 @@ type Controls<Layout extends string = string> = {
|
|
|
2412
2480
|
readonly available: boolean;
|
|
2413
2481
|
};
|
|
2414
2482
|
//#endregion
|
|
2415
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2483
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/lifecycle.d.ts
|
|
2416
2484
|
/** @group Lifecycle */
|
|
2417
2485
|
type LifecycleListener = (phase: Phase, previous: Phase) => void | Promise<void>;
|
|
2418
2486
|
/**
|
|
@@ -2431,7 +2499,7 @@ type Lifecycle = {
|
|
|
2431
2499
|
on(listener: LifecycleListener): Unsubscribe;
|
|
2432
2500
|
};
|
|
2433
2501
|
//#endregion
|
|
2434
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2502
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/loop.d.ts
|
|
2435
2503
|
/**
|
|
2436
2504
|
* The host has let the game run its loop, or stopped it. Return a promise to
|
|
2437
2505
|
* make the host wait.
|
|
@@ -2485,7 +2553,7 @@ type Loop = {
|
|
|
2485
2553
|
tick(frame: FrameCallback): Unsubscribe;
|
|
2486
2554
|
};
|
|
2487
2555
|
//#endregion
|
|
2488
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2556
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/panel.d.ts
|
|
2489
2557
|
/**
|
|
2490
2558
|
* The pages of the host's panel a game has a name for, as the keys of this
|
|
2491
2559
|
* interface.
|
|
@@ -2623,7 +2691,7 @@ type Panel = {
|
|
|
2623
2691
|
open(page: PageId): void;
|
|
2624
2692
|
};
|
|
2625
2693
|
//#endregion
|
|
2626
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2694
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/pause.d.ts
|
|
2627
2695
|
/**
|
|
2628
2696
|
* The host has asked for a pause, or for the game to carry on.
|
|
2629
2697
|
*
|
|
@@ -2684,7 +2752,7 @@ type Pause = {
|
|
|
2684
2752
|
on(listener: PauseListener): Unsubscribe;
|
|
2685
2753
|
};
|
|
2686
2754
|
//#endregion
|
|
2687
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2755
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/resources.d.ts
|
|
2688
2756
|
/**
|
|
2689
2757
|
* The host has let the game hold its resources again, or asked for them back.
|
|
2690
2758
|
* Return a promise to make the host wait until they have gone, or come back.
|
|
@@ -2714,7 +2782,7 @@ type Resources = {
|
|
|
2714
2782
|
on(listener: ResourcesListener): Unsubscribe;
|
|
2715
2783
|
};
|
|
2716
2784
|
//#endregion
|
|
2717
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2785
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/save.d.ts
|
|
2718
2786
|
/**
|
|
2719
2787
|
* What a game gets back from a read: its own schema number, and its state.
|
|
2720
2788
|
*
|
|
@@ -2851,7 +2919,7 @@ type Save = {
|
|
|
2851
2919
|
readonly maxBytes: number;
|
|
2852
2920
|
};
|
|
2853
2921
|
//#endregion
|
|
2854
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2922
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/score.d.ts
|
|
2855
2923
|
/**
|
|
2856
2924
|
* Where this player stands on every board, as far as anybody has said.
|
|
2857
2925
|
*
|
|
@@ -2928,7 +2996,7 @@ type Score<Board extends string = string> = {
|
|
|
2928
2996
|
}): Promise<ScoreResult>;
|
|
2929
2997
|
};
|
|
2930
2998
|
//#endregion
|
|
2931
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2999
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/share.d.ts
|
|
2932
3000
|
/**
|
|
2933
3001
|
* @group Share
|
|
2934
3002
|
* @category Important
|
|
@@ -3000,7 +3068,7 @@ type Share = {
|
|
|
3000
3068
|
}): void;
|
|
3001
3069
|
};
|
|
3002
3070
|
//#endregion
|
|
3003
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
3071
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/init.d.ts
|
|
3004
3072
|
/**
|
|
3005
3073
|
* What `init` hands back: every namespace a game has, on one object.
|
|
3006
3074
|
*
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @hopple/sdk 1.0.0-next.
|
|
1
|
+
/*! @hopple/sdk 1.0.0-next.4 | 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-
|
|
1
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/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.
|
|
@@ -671,6 +671,20 @@ type ControlSlot = {
|
|
|
671
671
|
as?: InputStick | Record<string, InputBinding>;
|
|
672
672
|
/** `thumbstick` only. See `ControlAxis`. Absent, the stick moves both ways. */
|
|
673
673
|
axis?: ControlAxis;
|
|
674
|
+
/**
|
|
675
|
+
* How big this control is next to the layout's others: `1.25` for a jump
|
|
676
|
+
* button a thumb lands on without looking, `0.8` for one pressed now and then.
|
|
677
|
+
* Absent, 1.
|
|
678
|
+
*
|
|
679
|
+
* A size in the layout rather than in the game's stylesheet, because whether
|
|
680
|
+
* the layout fits on a small screen is worked out from it before the
|
|
681
|
+
* controls are drawn. A game's stylesheet changes how they look, never how
|
|
682
|
+
* much of the screen they answer in.
|
|
683
|
+
*
|
|
684
|
+
* @minimum 0.75
|
|
685
|
+
* @maximum 1.5
|
|
686
|
+
*/
|
|
687
|
+
scale?: number;
|
|
674
688
|
};
|
|
675
689
|
/**
|
|
676
690
|
* The one direction an on-screen thumbstick moves in: `x` across, `y` up and
|
|
@@ -821,6 +835,23 @@ type PreviewsDeclaration = {
|
|
|
821
835
|
*/
|
|
822
836
|
network?: PreviewRevision;
|
|
823
837
|
};
|
|
838
|
+
/**
|
|
839
|
+
* What kind of game this is, as one word from a list every game shares.
|
|
840
|
+
*
|
|
841
|
+
* A closed list, because a client files games under these and has to name each
|
|
842
|
+
* one in front of a player: left to free text, one kind of game becomes three
|
|
843
|
+
* spellings of it with a game each. The list only grows, since a published
|
|
844
|
+
* manifest has to stay valid. Whoever reads one may be older than the game, and
|
|
845
|
+
* ignores a word it does not know.
|
|
846
|
+
*
|
|
847
|
+
* `local-multiplayer` and `online-multiplayer` say how a game is played rather
|
|
848
|
+
* than what it is, and only count where the manifest bears them out: see
|
|
849
|
+
* `declaredGenres`.
|
|
850
|
+
*
|
|
851
|
+
* @group Manifest
|
|
852
|
+
* @category Useful
|
|
853
|
+
*/
|
|
854
|
+
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';
|
|
824
855
|
/**
|
|
825
856
|
* Publishing refuses a key the manifest's API does not know; a host reading a
|
|
826
857
|
* published one ignores it (see `validateManifest`).
|
|
@@ -860,6 +891,15 @@ type GameManifest = {
|
|
|
860
891
|
* @errorMessage must be a handle: lowercase letters, digits and hyphens
|
|
861
892
|
*/
|
|
862
893
|
owner?: string;
|
|
894
|
+
/**
|
|
895
|
+
* `false` for a game that is never to be uploaded: an example, a test, a
|
|
896
|
+
* game kept to one machine. `hopple dev` and `hopple check` still run it;
|
|
897
|
+
* `hopple upload` and `hopple publish` refuse it, and so does a server it
|
|
898
|
+
* is sent to another way. Absent, it may be published.
|
|
899
|
+
*
|
|
900
|
+
* @category Useful
|
|
901
|
+
*/
|
|
902
|
+
publishable?: boolean;
|
|
863
903
|
/**
|
|
864
904
|
* The game's own version, `MAJOR.MINOR.PATCH`. Ours to read, never to parse for meaning.
|
|
865
905
|
*
|
|
@@ -884,6 +924,18 @@ type GameManifest = {
|
|
|
884
924
|
* @errorMessage must not be blank
|
|
885
925
|
*/
|
|
886
926
|
description?: string;
|
|
927
|
+
/**
|
|
928
|
+
* What kind of game it is, the main one first. Read by whoever files it in a
|
|
929
|
+
* catalogue, which never runs it. At most `GENRE_MAX`, not counting
|
|
930
|
+
* `local-multiplayer` and `online-multiplayer`: a game that is every kind
|
|
931
|
+
* tells a player nothing. The publisher's claim, taken at its word, except
|
|
932
|
+
* for those two. See `GameGenre`.
|
|
933
|
+
*
|
|
934
|
+
* @category Useful
|
|
935
|
+
* @minItems 1
|
|
936
|
+
* @maxItems 5
|
|
937
|
+
*/
|
|
938
|
+
genres?: GameGenre[];
|
|
887
939
|
/**
|
|
888
940
|
* A picture of the game, shown wherever a link to it is pasted — a chat, a
|
|
889
941
|
* post, a message.
|
package/manifest.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @hopple/sdk 1.0.0-next.
|
|
1
|
+
/*! @hopple/sdk 1.0.0-next.4 | Copyright (c) 2026 The Hopple authors | MIT License */
|
|
2
2
|
//#region ../manifest/src/manifest.schema.ts
|
|
3
3
|
const GameManifest = {
|
|
4
4
|
"type": "object",
|
|
@@ -16,6 +16,7 @@ const GameManifest = {
|
|
|
16
16
|
"maxLength": 64,
|
|
17
17
|
"errorMessage": "must be a handle: lowercase letters, digits and hyphens"
|
|
18
18
|
},
|
|
19
|
+
"publishable": { "type": "boolean" },
|
|
19
20
|
"version": {
|
|
20
21
|
"type": "string",
|
|
21
22
|
"pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
|
|
@@ -33,6 +34,12 @@ const GameManifest = {
|
|
|
33
34
|
"maxLength": 500,
|
|
34
35
|
"errorMessage": "must not be blank"
|
|
35
36
|
},
|
|
37
|
+
"genres": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": { "$ref": "#/definitions/GameGenre" },
|
|
40
|
+
"minItems": 1,
|
|
41
|
+
"maxItems": 5
|
|
42
|
+
},
|
|
36
43
|
"cover": {
|
|
37
44
|
"type": "string",
|
|
38
45
|
"pattern": "^(?!/)(?!.*\\.\\.)[A-Za-z0-9._/-]+\\.(?:png|jpe?g|webp)$",
|
|
@@ -65,6 +72,32 @@ const ApiVersion = {
|
|
|
65
72
|
"pattern": "^(?:(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)|next)$",
|
|
66
73
|
"errorMessage": "must be the API the game is written against, like \"1.0\", or \"next\""
|
|
67
74
|
};
|
|
75
|
+
const GameGenre = {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": [
|
|
78
|
+
"racing",
|
|
79
|
+
"shooter",
|
|
80
|
+
"platformer",
|
|
81
|
+
"puzzle",
|
|
82
|
+
"arcade",
|
|
83
|
+
"physics",
|
|
84
|
+
"roguelike",
|
|
85
|
+
"strategy",
|
|
86
|
+
"building",
|
|
87
|
+
"idle",
|
|
88
|
+
"sports",
|
|
89
|
+
"horror",
|
|
90
|
+
"stealth",
|
|
91
|
+
"fighting",
|
|
92
|
+
"casual",
|
|
93
|
+
"simulation",
|
|
94
|
+
"adventure",
|
|
95
|
+
"rhythm",
|
|
96
|
+
"rpg",
|
|
97
|
+
"local-multiplayer",
|
|
98
|
+
"online-multiplayer"
|
|
99
|
+
]
|
|
100
|
+
};
|
|
68
101
|
const Orientation = {
|
|
69
102
|
"type": "string",
|
|
70
103
|
"enum": [
|
|
@@ -372,7 +405,12 @@ const ControlSlot = {
|
|
|
372
405
|
"type": "object",
|
|
373
406
|
"additionalProperties": { "$ref": "#/definitions/InputBinding" }
|
|
374
407
|
}] },
|
|
375
|
-
"axis": { "$ref": "#/definitions/ControlAxis" }
|
|
408
|
+
"axis": { "$ref": "#/definitions/ControlAxis" },
|
|
409
|
+
"scale": {
|
|
410
|
+
"type": "number",
|
|
411
|
+
"minimum": .75,
|
|
412
|
+
"maximum": 1.5
|
|
413
|
+
}
|
|
376
414
|
},
|
|
377
415
|
"required": ["type"],
|
|
378
416
|
"additionalProperties": false,
|
|
@@ -412,6 +450,7 @@ const InputStick = {
|
|
|
412
450
|
const definitions = {
|
|
413
451
|
GameManifest,
|
|
414
452
|
ApiVersion,
|
|
453
|
+
GameGenre,
|
|
415
454
|
Orientation,
|
|
416
455
|
LayoutDeclaration,
|
|
417
456
|
InsetMode,
|
|
@@ -573,6 +612,7 @@ const definitions = {
|
|
|
573
612
|
};
|
|
574
613
|
AchievementProgress.properties.target.maximum;
|
|
575
614
|
GameManifest.required;
|
|
615
|
+
GameGenre.enum;
|
|
576
616
|
/**
|
|
577
617
|
* The zones a layout places controls in, in the order they are drawn.
|
|
578
618
|
*
|
|
@@ -750,6 +790,9 @@ function walkObject(node, value, path, fail, lenient) {
|
|
|
750
790
|
}
|
|
751
791
|
}
|
|
752
792
|
function checkReferences(manifest, fail) {
|
|
793
|
+
const genres = manifest.genres ?? [];
|
|
794
|
+
for (const genre of new Set(genres.filter((one, index) => genres.indexOf(one) !== index))) fail("genres", `names "${genre}" twice`);
|
|
795
|
+
if (genres.filter((genre) => !MULTIPLAYER_GENRES.includes(genre)).length > 3) fail("genres", `must hold at most 3 genres, beside local-multiplayer and online-multiplayer`);
|
|
753
796
|
const players = manifest.input?.player;
|
|
754
797
|
const own = new Set(Object.keys(players?.actions ?? {}));
|
|
755
798
|
const actions = /* @__PURE__ */ new Set([...Object.keys(manifest.input?.actions ?? {}), ...own]);
|
|
@@ -787,6 +830,8 @@ function checkReferences(manifest, fail) {
|
|
|
787
830
|
}
|
|
788
831
|
}
|
|
789
832
|
}
|
|
833
|
+
/** The genres that say how a game is played together rather than what it is. */
|
|
834
|
+
const MULTIPLAYER_GENRES = ["local-multiplayer", "online-multiplayer"];
|
|
790
835
|
/**
|
|
791
836
|
* Whether `a` beats `b` on a board sorted this way. Ties are not improvements.
|
|
792
837
|
*
|
package/manifest.md
CHANGED
|
@@ -35,6 +35,7 @@ leave `api` and `previews` out: the CLI fills `api` from the installed
|
|
|
35
35
|
| `version` | `MAJOR.MINOR.PATCH`, optional `-prerelease` | The game's own version. Shown to the game as `sdk.info.game.version`. |
|
|
36
36
|
| `title` | at most 60 characters | What players see. |
|
|
37
37
|
| `description` | optional; at most 500 characters | A sentence or two shown beside the title. |
|
|
38
|
+
| `genres` | optional; see [`genres`](#genres) | What kind of game it is, the main one first. |
|
|
38
39
|
| `orientation` | `portrait`, `landscape` or `any` | The shape the game is designed for. Hosts lay the game out to match. |
|
|
39
40
|
| `audio` | `null`, or an object | Whether the game makes sound, and its volume channels. See [`audio`](#audio). |
|
|
40
41
|
|
|
@@ -219,7 +220,8 @@ up to 8 characters, 8 tiers.
|
|
|
219
220
|
`buttons` (naming actions) arranged as a `row`, `column`, `diamond` or
|
|
220
221
|
`cluster`. An unknown shape is drawn as a column. A stick held to an axis is
|
|
221
222
|
drawn as a track and always reads 0 on the other axis; a real pad's stick
|
|
222
|
-
still reports both.
|
|
223
|
+
still reports both. Any control may have a `scale` from 0.75 to 1.5, its
|
|
224
|
+
size next to the layout's others (1 when left out).
|
|
223
225
|
|
|
224
226
|
Binding names:
|
|
225
227
|
|
|
@@ -295,6 +297,31 @@ Each channel gets its own labelled volume slider in the host's sound controls.
|
|
|
295
297
|
`title` is required: it is the slider's label. `master` is reserved, since
|
|
296
298
|
every host already has a master volume. At most 8 channels.
|
|
297
299
|
|
|
300
|
+
### genres
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
"genres": ["puzzle", "shooter", "online-multiplayer"]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
What kind of game this is, the main one first: at least one, and at most 3
|
|
307
|
+
beside the two multiplayer words, none twice. A game with none declared is
|
|
308
|
+
filed by what its title and description say, and most often less well.
|
|
309
|
+
|
|
310
|
+
The words are `racing`, `shooter`, `platformer`, `puzzle`, `arcade`, `physics`,
|
|
311
|
+
`roguelike`, `strategy`, `building`, `idle`, `sports`, `horror`, `stealth`,
|
|
312
|
+
`fighting`, `casual`, `simulation`, `adventure`, `rhythm` and `rpg`, and two
|
|
313
|
+
that say how it is played together:
|
|
314
|
+
|
|
315
|
+
- **`local-multiplayer`**: several people on one device. Needs `players.local`
|
|
316
|
+
above 1.
|
|
317
|
+
- **`online-multiplayer`**: over the network. Needs `previews.network`.
|
|
318
|
+
|
|
319
|
+
Either of those without what it needs is not refused: the game is published,
|
|
320
|
+
that word is ignored, and `hopple check`, `hopple upload` and `hopple publish`
|
|
321
|
+
warn about it. They warn the other way round too, for a game that declares
|
|
322
|
+
`genres` and leaves out a way of playing together its manifest shows, since it
|
|
323
|
+
then is not listed as one. Clients group the two under one shelf.
|
|
324
|
+
|
|
298
325
|
### previews
|
|
299
326
|
|
|
300
327
|
```json
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hopple/sdk",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.4",
|
|
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",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/pamarit/hopple.git"
|
|
10
|
+
},
|
|
7
11
|
"keywords": [
|
|
8
12
|
"hopple",
|
|
9
13
|
"game",
|