@hopple/sdk 0.1.0-rc.3 → 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 +144 -27
- package/config.schema.json +261 -41
- package/index.d.ts +578 -879
- package/index.js +2 -29
- package/manifest.d.ts +1063 -0
- package/manifest.js +896 -0
- package/manifest.md +101 -11
- package/package.json +9 -1
package/index.d.ts
CHANGED
|
@@ -1,34 +1,4 @@
|
|
|
1
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2
|
-
/**
|
|
3
|
-
* The largest value progress will carry.
|
|
4
|
-
*
|
|
5
|
-
* Spelled here *and* in `@hopple/manifest`, on purpose and with a test to keep
|
|
6
|
-
* them equal — the server checks the same rule and is not allowed to depend on
|
|
7
|
-
* this package.
|
|
8
|
-
*
|
|
9
|
-
* @group Achievements
|
|
10
|
-
*/
|
|
11
|
-
declare const ACHIEVEMENT_VALUE_MAX = 9007199254740991;
|
|
12
|
-
/**
|
|
13
|
-
* What the host says came of a report.
|
|
14
|
-
*
|
|
15
|
-
* `earned` is the transition and the only thing worth drawing a banner on: a
|
|
16
|
-
* game that unlocks the same achievement on every level-up gets it once. A host
|
|
17
|
-
* that took the report answers both fields from this player's own history, so
|
|
18
|
-
* unlike a score's rank there is nothing here a host might not know.
|
|
19
|
-
*
|
|
20
|
-
* @group Achievements
|
|
21
|
-
*/
|
|
22
|
-
type AchievementResult = {
|
|
23
|
-
/** Whether it is theirs, after this call. */
|
|
24
|
-
unlocked: boolean;
|
|
25
|
-
/** Whether *this call* is what unlocked it. False for one already held. */
|
|
26
|
-
earned: boolean;
|
|
27
|
-
/** The best value, after this call. Absent for a flagged achievement. */
|
|
28
|
-
progress?: number;
|
|
29
|
-
};
|
|
30
|
-
//#endregion
|
|
31
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-DAYaix/protocol/src/analytics.d.ts
|
|
1
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/analytics.d.ts
|
|
32
2
|
/**
|
|
33
3
|
* What a game says happened.
|
|
34
4
|
*
|
|
@@ -59,166 +29,77 @@ type AnalyticsValue = string | number | boolean;
|
|
|
59
29
|
/** @group Analytics */
|
|
60
30
|
type AnalyticsProps = Record<string, AnalyticsValue>;
|
|
61
31
|
//#endregion
|
|
62
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
63
|
-
/**
|
|
64
|
-
* How loud the game is allowed to be, which is the host's to decide and the
|
|
65
|
-
* game's to obey.
|
|
66
|
-
*
|
|
67
|
-
* **This is the one namespace where the host is not offering a service.** A
|
|
68
|
-
* save, a score, a link and an achievement are all things a game asks for and a
|
|
69
|
-
* host may refuse; audio runs the other way. The host states the volumes, and a
|
|
70
|
-
* game that ignores them is a game that plays music over a phone call, under an
|
|
71
|
-
* advertisement, and in a library. So there is no `unsupported` here and no way
|
|
72
|
-
* to decline: the only thing a game does with this is apply it.
|
|
73
|
-
*
|
|
74
|
-
* **Nothing about sound crosses the bridge.** The samples, the mixing and the
|
|
75
|
-
* decoding all happen in the frame, exactly as presses do — what travels is the
|
|
76
|
-
* three numbers the frame cannot know, because they belong to a player using a
|
|
77
|
-
* host rather than to a game.
|
|
78
|
-
*
|
|
79
|
-
* **The channels are named by the manifest and not described here.** What
|
|
80
|
-
* `music` means is the game's, and the host learned the id and its title from
|
|
81
|
-
* the file it was published with. So the wire carries volumes keyed by id and
|
|
82
|
-
* nothing else, and a host newer than a game — or a game whose next version
|
|
83
|
-
* renamed a channel — produces an id the other side simply has no fader for.
|
|
84
|
-
*/
|
|
32
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/errors.d.ts
|
|
85
33
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* A fraction rather than decibels, because it is what a fader hands over and
|
|
89
|
-
* what a gain node takes, and neither end of this wire is the place to decide
|
|
90
|
-
* on a curve. A host that wants one applies it before it says a number; a game
|
|
91
|
-
* that wants one applies it after. Both are right, and the wire stays a
|
|
92
|
-
* fraction either way.
|
|
34
|
+
* How a call fails.
|
|
93
35
|
*
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
* The channel every host has and no game declares. See `AUDIO_MASTER` in
|
|
99
|
-
* `@hopple/manifest`, which is the same word said where a manifest can be
|
|
100
|
-
* checked against it.
|
|
36
|
+
* Requests reject; nothing throws synchronously once a peer exists. The codes
|
|
37
|
+
* are the stable part — a game built against a newer SDK than the host it lands
|
|
38
|
+
* on must be able to tell why it was refused and degrade, rather than take an
|
|
39
|
+
* unhandled rejection into the player's face.
|
|
101
40
|
*
|
|
102
|
-
*
|
|
41
|
+
* Codes are added as something starts producing them, never in advance: a code
|
|
42
|
+
* nothing can emit tells a game to handle a case that cannot happen. Adding one
|
|
43
|
+
* is additive and safe.
|
|
103
44
|
*/
|
|
104
|
-
declare const AUDIO_MASTER = "master";
|
|
105
45
|
/**
|
|
106
|
-
*
|
|
46
|
+
* The codes a call fails with, as the keys of this interface.
|
|
107
47
|
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* that stored it as one would hand the player back silence.
|
|
48
|
+
* An interface rather than a union so that a preview can add the codes only
|
|
49
|
+
* its calls fail with, as it adds its own namespace to the SDK: a game is
|
|
50
|
+
* named the codes of what it uses.
|
|
112
51
|
*
|
|
113
|
-
*
|
|
114
|
-
* `layout.change` carries the whole layout: these are small, they arrive
|
|
115
|
-
* rarely, and a game that missed one — because it was loading, because a host
|
|
116
|
-
* coalesced two — must not end up mixing from halves.
|
|
117
|
-
*
|
|
118
|
-
* @group Audio
|
|
52
|
+
* @group Bridge
|
|
119
53
|
*/
|
|
120
|
-
|
|
54
|
+
interface ErrorCodes {
|
|
55
|
+
/** No handler on the other side. Usually means the host predates the method. */
|
|
56
|
+
unknown_method: true;
|
|
57
|
+
/** The handler rejected the shape of `params`. */
|
|
58
|
+
bad_params: true;
|
|
59
|
+
/** No reply inside the peer's timeout. Says nothing about whether it ran. */
|
|
60
|
+
timeout: true;
|
|
61
|
+
/** The peer was torn down with the call still in flight — a recycled slot. */
|
|
62
|
+
disposed: true;
|
|
121
63
|
/**
|
|
122
|
-
*
|
|
64
|
+
* The call belonged to a session that has ended: the game started over inside
|
|
65
|
+
* a frame the host still holds. Distinct from `disposed` because the bridge is
|
|
66
|
+
* alive and the next call will work — it is this one that has nobody left to
|
|
67
|
+
* answer it.
|
|
68
|
+
*/
|
|
69
|
+
stale: true;
|
|
70
|
+
/** The handshake asked for a protocol version this side cannot speak. */
|
|
71
|
+
unsupported_version: true;
|
|
72
|
+
/**
|
|
73
|
+
* This host does not do that at all — a site with no store answering
|
|
74
|
+
* `save.write`.
|
|
123
75
|
*
|
|
124
|
-
*
|
|
125
|
-
* the
|
|
126
|
-
*
|
|
76
|
+
* A different fact from `unknown_method`, which says the host is *older* than
|
|
77
|
+
* the method, and a game acts on the difference: one is a host that will
|
|
78
|
+
* never save and one is a host that would if it were newer. Neither is worth
|
|
79
|
+
* retrying, and both are worth saying honestly.
|
|
127
80
|
*/
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
|
|
81
|
+
unsupported: true;
|
|
82
|
+
/** Over this host's cap. Says trim, rather than go looking for a bug. */
|
|
83
|
+
too_large: true;
|
|
131
84
|
/**
|
|
132
|
-
*
|
|
85
|
+
* Faster than this host will take. The counterpart of the analytics refusal.
|
|
133
86
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
87
|
+
* Never what a host that *coalesced* two writes says: a write superseded by
|
|
88
|
+
* newer state from the same game has not been lost, and both promises
|
|
89
|
+
* resolve. This is for a host actually dropping writes, which usually means a
|
|
90
|
+
* game is saving from a render loop by mistake.
|
|
138
91
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
* for it on every host that has one.
|
|
149
|
-
*
|
|
150
|
-
* @group Audio
|
|
151
|
-
*/
|
|
152
|
-
declare const AUDIO_FULL: AudioState;
|
|
153
|
-
/**
|
|
154
|
-
* How loud one channel actually comes out, which is the only number a game
|
|
155
|
-
* needs and the one both sides must agree on.
|
|
156
|
-
*
|
|
157
|
-
* Here rather than in the game's SDK because the dev shell draws it too — a
|
|
158
|
-
* developer reading *music: 0.35* off a panel and hearing something else has no
|
|
159
|
-
* way to tell which half is wrong. Multiplication and not the smaller of the
|
|
160
|
-
* two: a master fader is a fader over the mix, so halving it halves everything
|
|
161
|
-
* under it rather than capping the loudest.
|
|
162
|
-
*
|
|
163
|
-
* @group Audio
|
|
164
|
-
*/
|
|
165
|
-
declare function audioVolumeOf(state: AudioState, channel?: string): number;
|
|
166
|
-
//#endregion
|
|
167
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-DAYaix/protocol/src/errors.d.ts
|
|
168
|
-
/**
|
|
169
|
-
* How a call fails.
|
|
170
|
-
*
|
|
171
|
-
* Requests reject; nothing throws synchronously once a peer exists. The codes
|
|
172
|
-
* are the stable part — a game built against a newer SDK than the host it lands
|
|
173
|
-
* on must be able to tell why it was refused and degrade, rather than take an
|
|
174
|
-
* unhandled rejection into the player's face.
|
|
175
|
-
*
|
|
176
|
-
* Codes are added as something starts producing them, never in advance: a code
|
|
177
|
-
* nothing can emit tells a game to handle a case that cannot happen. Adding one
|
|
178
|
-
* is additive and safe.
|
|
179
|
-
*/
|
|
92
|
+
rate_limited: true;
|
|
93
|
+
/**
|
|
94
|
+
* Not now: what it needs can't be reached for the moment. Worth trying
|
|
95
|
+
* again shortly.
|
|
96
|
+
*/
|
|
97
|
+
unavailable: true;
|
|
98
|
+
/** The handler threw. The message is ours, never the thrown value's. */
|
|
99
|
+
internal: true;
|
|
100
|
+
}
|
|
180
101
|
/** @group Bridge */
|
|
181
|
-
type ErrorCode =
|
|
182
|
-
/** No handler on the other side. Usually means the host predates the method. */
|
|
183
|
-
'unknown_method' |
|
|
184
|
-
/** The handler rejected the shape of `params`. */
|
|
185
|
-
'bad_params' |
|
|
186
|
-
/** No reply inside the peer's timeout. Says nothing about whether it ran. */
|
|
187
|
-
'timeout' |
|
|
188
|
-
/** The peer was torn down with the call still in flight — a recycled slot. */
|
|
189
|
-
'disposed' |
|
|
190
|
-
/**
|
|
191
|
-
* The call belonged to a session that has ended: the game started over inside
|
|
192
|
-
* a frame the host still holds. Distinct from `disposed` because the bridge is
|
|
193
|
-
* alive and the next call will work — it is this one that has nobody left to
|
|
194
|
-
* answer it.
|
|
195
|
-
*/
|
|
196
|
-
'stale' |
|
|
197
|
-
/** The handshake asked for a protocol version this side cannot speak. */
|
|
198
|
-
'unsupported_version' |
|
|
199
|
-
/**
|
|
200
|
-
* This host does not do that at all — a site with no store answering
|
|
201
|
-
* `save.write`.
|
|
202
|
-
*
|
|
203
|
-
* A different fact from `unknown_method`, which says the host is *older* than
|
|
204
|
-
* the method, and a game acts on the difference: one is a host that will
|
|
205
|
-
* never save and one is a host that would if it were newer. Neither is worth
|
|
206
|
-
* retrying, and both are worth saying honestly.
|
|
207
|
-
*/
|
|
208
|
-
'unsupported' |
|
|
209
|
-
/** Over this host's cap. Says trim, rather than go looking for a bug. */
|
|
210
|
-
'too_large' |
|
|
211
|
-
/**
|
|
212
|
-
* Faster than this host will take. The counterpart of the analytics refusal.
|
|
213
|
-
*
|
|
214
|
-
* Never what a host that *coalesced* two writes says: a write superseded by
|
|
215
|
-
* newer state from the same game has not been lost, and both promises
|
|
216
|
-
* resolve. This is for a host actually dropping writes, which usually means a
|
|
217
|
-
* game is saving from a render loop by mistake.
|
|
218
|
-
*/
|
|
219
|
-
'rate_limited' |
|
|
220
|
-
/** The handler threw. The message is ours, never the thrown value's. */
|
|
221
|
-
'internal';
|
|
102
|
+
type ErrorCode = keyof ErrorCodes;
|
|
222
103
|
/** @group Bridge */
|
|
223
104
|
type ProtocolError = {
|
|
224
105
|
code: ErrorCode;
|
|
@@ -234,48 +115,7 @@ declare class BridgeError extends Error {
|
|
|
234
115
|
constructor(error: ProtocolError);
|
|
235
116
|
}
|
|
236
117
|
//#endregion
|
|
237
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
238
|
-
/**
|
|
239
|
-
* `p` is the protocol version, carried on every message rather than only at the
|
|
240
|
-
* handshake, because a message that arrives without context still has to be
|
|
241
|
-
* classifiable.
|
|
242
|
-
*
|
|
243
|
-
* There is deliberately no addressing. A bridge joins exactly one host to
|
|
244
|
-
* exactly one game: the game lives alone in its frame and has no way to name
|
|
245
|
-
* another game, and the host holds one frame and needs no way to name another.
|
|
246
|
-
* A host that runs several games at once — the app, the production site — runs
|
|
247
|
-
* several bridges, each with its own transport, and keeping their inboxes apart
|
|
248
|
-
* is that host's problem with its own frames. Putting a channel in the envelope
|
|
249
|
-
* would make every transport pay for a problem only one kind of host has.
|
|
250
|
-
*
|
|
251
|
-
* @group Bridge
|
|
252
|
-
*/
|
|
253
|
-
type Envelope = {
|
|
254
|
-
p: 1;
|
|
255
|
-
kind: 'req';
|
|
256
|
-
id: string;
|
|
257
|
-
method: string;
|
|
258
|
-
params: unknown;
|
|
259
|
-
} | {
|
|
260
|
-
p: 1;
|
|
261
|
-
kind: 'res';
|
|
262
|
-
id: string;
|
|
263
|
-
ok: true;
|
|
264
|
-
value: unknown;
|
|
265
|
-
} | {
|
|
266
|
-
p: 1;
|
|
267
|
-
kind: 'res';
|
|
268
|
-
id: string;
|
|
269
|
-
ok: false;
|
|
270
|
-
error: ProtocolError;
|
|
271
|
-
} | {
|
|
272
|
-
p: 1;
|
|
273
|
-
kind: 'evt';
|
|
274
|
-
topic: string;
|
|
275
|
-
data: unknown;
|
|
276
|
-
};
|
|
277
|
-
//#endregion
|
|
278
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-DAYaix/protocol/src/input.d.ts
|
|
118
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/input.d.ts
|
|
279
119
|
/**
|
|
280
120
|
* What the host says about how this game is being played.
|
|
281
121
|
*
|
|
@@ -312,8 +152,6 @@ type Envelope = {
|
|
|
312
152
|
* @group Input
|
|
313
153
|
*/
|
|
314
154
|
type InputScheme = 'keyboard' | 'mouse' | 'touch' | 'gamepad';
|
|
315
|
-
/** @group Input */
|
|
316
|
-
declare const INPUT_SCHEMES: readonly InputScheme[];
|
|
317
155
|
/**
|
|
318
156
|
* What presses one action, and what to call it in front of a player.
|
|
319
157
|
*
|
|
@@ -336,87 +174,8 @@ type InputBinding = {
|
|
|
336
174
|
/** What to show for the binding a player would actually use. See above. */
|
|
337
175
|
label?: string;
|
|
338
176
|
};
|
|
339
|
-
/**
|
|
340
|
-
* How a group of on-screen buttons is arranged. See `ControlShape` in
|
|
341
|
-
* `@hopple/manifest`, which a test keeps this in step with.
|
|
342
|
-
*
|
|
343
|
-
* @group Input
|
|
344
|
-
*/
|
|
345
|
-
type InputLayoutShape = 'row' | 'column' | 'diamond' | 'cluster';
|
|
346
|
-
/**
|
|
347
|
-
* Where on the screen a group of on-screen controls sits. See `ControlZone` in
|
|
348
|
-
* `@hopple/manifest`.
|
|
349
|
-
*
|
|
350
|
-
* @group Input
|
|
351
|
-
*/
|
|
352
|
-
type InputLayoutZone = 'left' | 'right' | 'full';
|
|
353
|
-
/**
|
|
354
|
-
* The one direction an on-screen thumbstick moves in. See `ControlAxis` in
|
|
355
|
-
* `@hopple/manifest`.
|
|
356
|
-
*
|
|
357
|
-
* @group Input
|
|
358
|
-
*/
|
|
359
|
-
type InputLayoutAxis = 'x' | 'y';
|
|
360
|
-
/**
|
|
361
|
-
* How an on-screen thumbstick meets a thumb: a player's setting, the same in
|
|
362
|
-
* every game they open.
|
|
363
|
-
*
|
|
364
|
-
* `floating` is marked where the layout puts it, as a sign that there is a
|
|
365
|
-
* stick, and comes to wherever a thumb lands on its side of the screen — so a
|
|
366
|
-
* player never has to find it, and the place they first touch is always the
|
|
367
|
-
* centre. A thumb that goes past its rim pulls it along, so turning back is
|
|
368
|
-
* never further than the stick is wide. `fixed` stays where it is drawn and is
|
|
369
|
-
* moved only from there.
|
|
370
|
-
*
|
|
371
|
-
* @group Input
|
|
372
|
-
*/
|
|
373
|
-
type InputSticks = 'floating' | 'fixed';
|
|
374
|
-
/**
|
|
375
|
-
* Why the mapping or the scheme is different from what the game was told before.
|
|
376
|
-
*
|
|
377
|
-
* @group Input
|
|
378
|
-
*/
|
|
379
|
-
type InputChangeCause =
|
|
380
|
-
/** A player changed a binding. */
|
|
381
|
-
'remap' |
|
|
382
|
-
/** They picked up a controller, or put one down. */
|
|
383
|
-
'scheme' |
|
|
384
|
-
/** The host revised it for its own reasons. */
|
|
385
|
-
'host' |
|
|
386
|
-
/** A player changed how the on-screen controls behave. */
|
|
387
|
-
'setting';
|
|
388
|
-
//#endregion
|
|
389
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-DAYaix/protocol/src/panel.d.ts
|
|
390
|
-
/**
|
|
391
|
-
* The pages of the panel this version of the protocol has a name for.
|
|
392
|
-
*
|
|
393
|
-
* **A vocabulary, not a capability.** Every one of these is a subject both ends
|
|
394
|
-
* already have words for elsewhere — a game's own description, its boards, its
|
|
395
|
-
* achievements, its bindings — so the names are the ones the hosts already use
|
|
396
|
-
* for the pages they draw, and a game author who reads "About" in the panel
|
|
397
|
-
* finds `about` here.
|
|
398
|
-
*
|
|
399
|
-
* It grows, and it only ever grows: a name means the same thing for ever,
|
|
400
|
-
* because a game ships and is never updated. A host older than a name answers
|
|
401
|
-
* `unsupported`, which is the same thing it says about a page it simply does
|
|
402
|
-
* not draw — and the right answer to both, because from the game's side they
|
|
403
|
-
* are one fact: that page is not going to open here.
|
|
404
|
-
*
|
|
405
|
-
* @group Panels
|
|
406
|
-
*/
|
|
407
|
-
type PageId =
|
|
408
|
-
/** What this game is: its title, its author, its description. */
|
|
409
|
-
'about' |
|
|
410
|
-
/** Who has scored what, on this game's boards. */
|
|
411
|
-
'scores' |
|
|
412
|
-
/** What has been unlocked, and how far along the rest is. */
|
|
413
|
-
'achievements' |
|
|
414
|
-
/** What this game is played with, and where a player rebinds it. */
|
|
415
|
-
'input';
|
|
416
|
-
/** @group Panels */
|
|
417
|
-
declare const PAGES: readonly PageId[];
|
|
418
177
|
//#endregion
|
|
419
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
178
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/pause.d.ts
|
|
420
179
|
/**
|
|
421
180
|
* Whether the game is holding still, which the host may ask for and only the
|
|
422
181
|
* game can answer.
|
|
@@ -473,23 +232,8 @@ type PauseState =
|
|
|
473
232
|
* that pressing it would do something.
|
|
474
233
|
*/
|
|
475
234
|
'unpausable';
|
|
476
|
-
/** @group Pause */
|
|
477
|
-
declare const PAUSE_STATES: readonly PauseState[];
|
|
478
|
-
/**
|
|
479
|
-
* The cautious reading, and the one every silence falls back to: a game that
|
|
480
|
-
* has said nothing cannot be paused.
|
|
481
|
-
*
|
|
482
|
-
* The opposite of the reading `AUDIO_FULL` takes, and for the same reason —
|
|
483
|
-
* both are the answer that claims the least. Guessing loud is safe for volume
|
|
484
|
-
* because a game that started silent would look broken; guessing `unpausable`
|
|
485
|
-
* is safe here because a host that drew Pause on a game with no pause would
|
|
486
|
-
* have promised a button that does nothing.
|
|
487
|
-
*
|
|
488
|
-
* @group Pause
|
|
489
|
-
*/
|
|
490
|
-
declare const PAUSE_NONE: PauseState;
|
|
491
235
|
//#endregion
|
|
492
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
236
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/save.d.ts
|
|
493
237
|
/**
|
|
494
238
|
* The one thing a game may keep.
|
|
495
239
|
*
|
|
@@ -540,45 +284,7 @@ type SaveStored = {
|
|
|
540
284
|
bytes: number;
|
|
541
285
|
};
|
|
542
286
|
//#endregion
|
|
543
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
544
|
-
/**
|
|
545
|
-
* What a player did, as one number.
|
|
546
|
-
*
|
|
547
|
-
* A game submits a score when a run ends; the host makes it durable and, if it
|
|
548
|
-
* has anywhere to send it, sends it. What comes back is what the player is
|
|
549
|
-
* shown: their own best, and where it stands.
|
|
550
|
-
*
|
|
551
|
-
* **The shape of this is not the shape of a save, and not the shape of
|
|
552
|
-
* analytics.** A save is one document rewritten in place, and losing one costs
|
|
553
|
-
* a player their progress. A score is an append — every run is its own fact, a
|
|
554
|
-
* later worse run does not erase a better one, and the collapsing into "your
|
|
555
|
-
* best" happens where the rows are rather than on the way in. Analytics is
|
|
556
|
-
* one-way because nothing waits for it; a score is a request because a game
|
|
557
|
-
* draws the answer.
|
|
558
|
-
*
|
|
559
|
-
* **The submission carries no player.** The game says which board and what
|
|
560
|
-
* number, and the host attaches who — which is the host being authoritative
|
|
561
|
-
* about the one fact a game must not be able to assert. It also means a game
|
|
562
|
-
* literally cannot submit on somebody else's behalf, which is free here and
|
|
563
|
-
* would be expensive to retrofit.
|
|
564
|
-
*
|
|
565
|
-
* **There is no anti-cheat and this file does not pretend otherwise.** A score
|
|
566
|
-
* is whatever the machine running the game says it is. What is checked is that
|
|
567
|
-
* the number is a number: finite, and inside the range where an integer still
|
|
568
|
-
* round-trips. The realistic thing that catches is an uninitialised variable.
|
|
569
|
-
*/
|
|
570
|
-
/**
|
|
571
|
-
* The largest score the wire will carry, and the smallest.
|
|
572
|
-
*
|
|
573
|
-
* Spelled here *and* in `@hopple/manifest`, on purpose and with a test to keep
|
|
574
|
-
* them equal. The server checks the same rule and is not allowed to depend on
|
|
575
|
-
* this package — "the server never talks to a game", made mechanical — so the
|
|
576
|
-
* rule is written down in the one package both sides may import and enforced on
|
|
577
|
-
* the wire here.
|
|
578
|
-
*
|
|
579
|
-
* @group Score
|
|
580
|
-
*/
|
|
581
|
-
declare const SCORE_VALUE_MAX = 9007199254740991;
|
|
287
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/score.d.ts
|
|
582
288
|
/**
|
|
583
289
|
* What the host says came of it.
|
|
584
290
|
*
|
|
@@ -616,7 +322,7 @@ type ScoreStanding = {
|
|
|
616
322
|
of?: number;
|
|
617
323
|
};
|
|
618
324
|
//#endregion
|
|
619
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
325
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/share.d.ts
|
|
620
326
|
/**
|
|
621
327
|
* A link to this game, offered to the player by the host.
|
|
622
328
|
*
|
|
@@ -715,7 +421,7 @@ type ShareMeta = {
|
|
|
715
421
|
[key: string]: ShareMetaValue;
|
|
716
422
|
};
|
|
717
423
|
//#endregion
|
|
718
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
424
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/methods.d.ts
|
|
719
425
|
/**
|
|
720
426
|
* How far a game has got with its own preparation.
|
|
721
427
|
*
|
|
@@ -895,7 +601,7 @@ type DeviceInfo = {
|
|
|
895
601
|
tier: DeviceTier;
|
|
896
602
|
};
|
|
897
603
|
//#endregion
|
|
898
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
604
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/protocol/src/listeners.d.ts
|
|
899
605
|
/**
|
|
900
606
|
* The one way anything here is listened to: `on(listener)` hands back the
|
|
901
607
|
* function that stops it.
|
|
@@ -924,152 +630,17 @@ type DeviceInfo = {
|
|
|
924
630
|
*/
|
|
925
631
|
type Unsubscribe = () => void;
|
|
926
632
|
//#endregion
|
|
927
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
928
|
-
/** @group Bridge */
|
|
929
|
-
type Transport = {
|
|
930
|
-
send(message: Envelope): void;
|
|
931
|
-
/** Returns the unsubscribe. Calling `receive` twice is not defined behaviour. */
|
|
932
|
-
receive(handler: (message: Envelope) => void): () => void;
|
|
933
|
-
close(): void;
|
|
934
|
-
};
|
|
935
|
-
/**
|
|
936
|
-
* The name a platform leaves its shim under, and the name it calls if it is late.
|
|
937
|
-
*
|
|
938
|
-
* Declared here, in the package both ends import, so that a platform author and
|
|
939
|
-
* the SDK are reading the same two strings. Nothing in this package writes
|
|
940
|
-
* either of them: the shim is wholly the platform's, and this file is a target
|
|
941
|
-
* to hit rather than code we ship.
|
|
942
|
-
*
|
|
943
|
-
* @group Bridge
|
|
944
|
-
*/
|
|
945
|
-
declare const SHIM = "__HOPPLE_SHIM__";
|
|
946
|
-
/** @group Bridge */
|
|
947
|
-
declare const SET_SHIM = "__HOPPLE_SET_SHIM__";
|
|
948
|
-
/**
|
|
949
|
-
* What a native shell leaves for a game to talk through.
|
|
950
|
-
*
|
|
951
|
-
* A game in a browser frame has `window.parent` and needs none of this. A game
|
|
952
|
-
* in a WebView is a top-level document — its `parent` is itself — so there is no
|
|
953
|
-
* window above it to post to, and the platform has to hand it a channel instead.
|
|
954
|
-
* This is that channel, reduced to the least a platform can be asked for.
|
|
955
|
-
*
|
|
956
|
-
* **The SDK writes none of it.** It agrees on a name and a shape and finds
|
|
957
|
-
* whatever was left; how the bytes actually cross is the platform's business,
|
|
958
|
-
* and is different on every one. That is what keeps this package free of hosts
|
|
959
|
-
* it has never heard of.
|
|
960
|
-
*
|
|
961
|
-
* A shim has exactly two duties:
|
|
962
|
-
*
|
|
963
|
-
* - **Arrive.** Before the game's bundle runs is easiest, and is what our own
|
|
964
|
-
* hosts do — a document-start hook, `injectedJavaScriptBeforeContentLoaded`,
|
|
965
|
-
* a `WKUserScript` at `.atDocumentStart`. A platform that cannot manage that
|
|
966
|
-
* is late rather than broken: see the rendezvous below.
|
|
967
|
-
* - **Never throw.** A shim that throws breaks a game that did nothing wrong.
|
|
968
|
-
*
|
|
969
|
-
* And one duty it explicitly does *not* have: **it must not buffer.** Nothing
|
|
970
|
-
* arrives before the game is listening, because every host → game message is
|
|
971
|
-
* gated on the handshake and the game is the side that opens it. An array in
|
|
972
|
-
* every shim would be paying, on every platform, for a message that is never
|
|
973
|
-
* sent.
|
|
974
|
-
*
|
|
975
|
-
* **Two ways in, and a platform may use either**, because neither side may
|
|
976
|
-
* assume it is first:
|
|
977
|
-
*
|
|
978
|
-
* ```js
|
|
979
|
-
* // The whole of what a platform has to get right about ordering.
|
|
980
|
-
* if (w.__HOPPLE_SET_SHIM__) w.__HOPPLE_SET_SHIM__(shim);
|
|
981
|
-
* else w.__HOPPLE_SHIM__ = shim;
|
|
982
|
-
* ```
|
|
983
|
-
*
|
|
984
|
-
* A platform that installs early always takes the second branch; one that
|
|
985
|
-
* cannot takes the first, and the SDK — which has been waiting — connects. The
|
|
986
|
-
* setter only exists while the SDK is waiting, and is removed once it is used
|
|
987
|
-
* or the wait expires.
|
|
988
|
-
*
|
|
989
|
-
* **Strings, not envelopes.** Every native bridge carries strings, so that is
|
|
990
|
-
* the contract, and it means the SDK stays the trust boundary: `JSON.parse` and
|
|
991
|
-
* `isEnvelope` run in SDK code on both sides of the wire and never in a
|
|
992
|
-
* platform's. A shim cannot get validation wrong, because it does none.
|
|
993
|
-
*
|
|
994
|
-
* @group Bridge
|
|
995
|
-
*/
|
|
996
|
-
type HoppleShim = {
|
|
997
|
-
/**
|
|
998
|
-
* The contract version. `1` today.
|
|
999
|
-
*
|
|
1000
|
-
* A shim whose version the SDK does not know is ignored rather than guessed
|
|
1001
|
-
* at — which falls through to the frame branch, and then to the wait.
|
|
1002
|
-
*/
|
|
1003
|
-
v: 1;
|
|
1004
|
-
/** Send one already-serialised envelope to the host. Game → host. */
|
|
1005
|
-
send(message: string): void;
|
|
1006
|
-
/**
|
|
1007
|
-
* Take the SDK's inbound handler, and hand back the unsubscribe.
|
|
1008
|
-
*
|
|
1009
|
-
* Called once, by the SDK, and it is a subscription rather than a delivery:
|
|
1010
|
-
* how the platform later reaches the handler it was given — an eval into a
|
|
1011
|
-
* global, a real message channel — is the platform's problem and is not
|
|
1012
|
-
* described here.
|
|
1013
|
-
*/
|
|
1014
|
-
receive(handler: (message: string) => void): () => void;
|
|
1015
|
-
};
|
|
1016
|
-
/**
|
|
1017
|
-
* The two globals, so a platform's own TypeScript sees the same shape the SDK
|
|
1018
|
-
* reads. Neither is ever written by this package.
|
|
1019
|
-
*/
|
|
1020
|
-
declare global {
|
|
1021
|
-
var __HOPPLE_SHIM__: HoppleShim | undefined;
|
|
1022
|
-
var __HOPPLE_SET_SHIM__: ((shim: HoppleShim) => void) | undefined;
|
|
1023
|
-
}
|
|
1024
|
-
//#endregion
|
|
1025
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-DAYaix/manifest/src/manifest.d.ts
|
|
633
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/manifest/src/manifest.d.ts
|
|
1026
634
|
/**
|
|
1027
|
-
*
|
|
1028
|
-
*
|
|
1029
|
-
* Written in the project's hopple config and uploaded beside the game's page
|
|
1030
|
-
* as `game.manifest.json`. It is the first thing the host learns: it is
|
|
1031
|
-
* published with the game, so a host holds it before it has anything to run —
|
|
1032
|
-
* from the server in the app, from the project in the dev server. It never
|
|
1033
|
-
* travels over the bridge. Not to be confused with `FeedEntry`, which is
|
|
1034
|
-
* what the *catalogue* says about a game.
|
|
1035
|
-
*
|
|
1036
|
-
* The core is deliberately tiny: identity, a name, and how the game wants to
|
|
1037
|
-
* be held. Everything a later API needs declared ahead of time
|
|
1038
|
-
* gets its own optional top-level key named after that API — `save`,
|
|
1039
|
-
* `achievements`, and so on — whose contents are designed by the milestone that
|
|
1040
|
-
* builds it. Most namespaces will never need one.
|
|
1041
|
-
*
|
|
1042
|
-
* **The types below are the only place a rule is written.** Their shapes and
|
|
1043
|
-
* the constraint tags in their comments — `@maxLength`, `@pattern` and the rest
|
|
1044
|
-
* — are compiled into `game.manifest.schema.json`, and `validateManifest` checks
|
|
1045
|
-
* a manifest by walking that schema. Change a type and the schema, the
|
|
1046
|
-
* validator and every editor that reads the schema follow. The only rules
|
|
1047
|
-
* written in code are the few no schema can state, and they are at the bottom
|
|
1048
|
-
* of this file.
|
|
1049
|
-
*
|
|
1050
|
-
* Validation lives here rather than in the host so that the publish CLI, the
|
|
1051
|
-
* dev server and the host all reach the same verdict about the same file.
|
|
1052
|
-
*
|
|
1053
|
-
* @module manifest
|
|
1054
|
-
*/
|
|
1055
|
-
/**
|
|
1056
|
-
* The name a manifest is uploaded under, beside the game's page.
|
|
1057
|
-
*
|
|
1058
|
-
* @group Manifest
|
|
1059
|
-
* @category Useful
|
|
1060
|
-
*/
|
|
1061
|
-
declare const MANIFEST_FILE = "game.manifest.json";
|
|
1062
|
-
/**
|
|
1063
|
-
* The manifest's own version, separate from the protocol's.
|
|
1064
|
-
*
|
|
1065
|
-
* Bumped only if the *core* changes meaning, which is a thing we intend never
|
|
1066
|
-
* to do — new keys are additive and need no bump. A host refuses a schema it
|
|
1067
|
-
* does not know, because a manifest it half-understands is worse than none.
|
|
635
|
+
* The API a game is written against: `M.m`, a major and the least minor it
|
|
636
|
+
* needs, or `next`, the pre-releases of the next stable release.
|
|
1068
637
|
*
|
|
1069
638
|
* @group Manifest
|
|
1070
|
-
* @category
|
|
639
|
+
* @category Important
|
|
640
|
+
* @pattern ^(?:(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)|next)$
|
|
641
|
+
* @errorMessage must be the API the game is written against, like "1.0", or "next"
|
|
1071
642
|
*/
|
|
1072
|
-
|
|
643
|
+
type ApiVersion = string;
|
|
1073
644
|
/**
|
|
1074
645
|
* How the game wants to be held. The host decides what to do about it.
|
|
1075
646
|
*
|
|
@@ -1100,7 +671,6 @@ type InsetMode =
|
|
|
1100
671
|
*/
|
|
1101
672
|
type LayoutDeclaration = {
|
|
1102
673
|
insets: InsetMode;
|
|
1103
|
-
[key: string]: unknown;
|
|
1104
674
|
};
|
|
1105
675
|
/**
|
|
1106
676
|
* What a prop may hold, as one word. The three the wire carries, and no more.
|
|
@@ -1152,7 +722,6 @@ type AnalyticsDeclaration = {
|
|
|
1152
722
|
* @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, not: { pattern: '^hopple\\.' }, errorMessage: 'must be lowercase letters, digits, dots, hyphens and underscores, and must not start with "hopple.", which the host uses' }
|
|
1153
723
|
*/
|
|
1154
724
|
events: Record<string, AnalyticsShape>;
|
|
1155
|
-
[key: string]: unknown;
|
|
1156
725
|
};
|
|
1157
726
|
/**
|
|
1158
727
|
* What a game expects to keep, in bytes.
|
|
@@ -1182,7 +751,6 @@ type SaveDeclaration = {
|
|
|
1182
751
|
* @maximum 1048576
|
|
1183
752
|
*/
|
|
1184
753
|
bytes: number;
|
|
1185
|
-
[key: string]: unknown;
|
|
1186
754
|
};
|
|
1187
755
|
/**
|
|
1188
756
|
* One thing a game makes noise with, as the player is offered it.
|
|
@@ -1204,7 +772,6 @@ type AudioChannel = {
|
|
|
1204
772
|
* @errorMessage must not be blank
|
|
1205
773
|
*/
|
|
1206
774
|
title: string;
|
|
1207
|
-
[key: string]: unknown;
|
|
1208
775
|
};
|
|
1209
776
|
/**
|
|
1210
777
|
* What this game makes noise with, so a host can offer a fader for each.
|
|
@@ -1245,7 +812,6 @@ type AudioDeclaration = {
|
|
|
1245
812
|
* @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, not: { const: 'master' }, errorMessage: 'must be lowercase letters, digits, dots, hyphens and underscores, and must not be "master", which every host adds itself' }
|
|
1246
813
|
*/
|
|
1247
814
|
channels?: Record<string, AudioChannel>;
|
|
1248
|
-
[key: string]: unknown;
|
|
1249
815
|
};
|
|
1250
816
|
/**
|
|
1251
817
|
* Which way a board sorts — the one thing about a score that cannot be guessed.
|
|
@@ -1283,7 +849,6 @@ type ScoreBoard = {
|
|
|
1283
849
|
direction: ScoreDirection;
|
|
1284
850
|
/** See `ScoreFormat`. Absent means `integer`. */
|
|
1285
851
|
format?: ScoreFormat;
|
|
1286
|
-
[key: string]: unknown;
|
|
1287
852
|
};
|
|
1288
853
|
/**
|
|
1289
854
|
* The boards this game keeps a score on.
|
|
@@ -1314,7 +879,6 @@ type ScoreDeclaration = {
|
|
|
1314
879
|
* @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be lowercase letters, digits, dots, hyphens and underscores' }
|
|
1315
880
|
*/
|
|
1316
881
|
boards: Record<string, ScoreBoard>;
|
|
1317
|
-
[key: string]: unknown;
|
|
1318
882
|
};
|
|
1319
883
|
/**
|
|
1320
884
|
* One named step on the way to a measured achievement's target.
|
|
@@ -1353,7 +917,6 @@ type AchievementTier = {
|
|
|
1353
917
|
* @errorMessage must not be blank
|
|
1354
918
|
*/
|
|
1355
919
|
icon?: string;
|
|
1356
|
-
[key: string]: unknown;
|
|
1357
920
|
};
|
|
1358
921
|
/**
|
|
1359
922
|
* What makes an achievement measured rather than flagged.
|
|
@@ -1399,7 +962,6 @@ type AchievementProgress = {
|
|
|
1399
962
|
* @maxItems 8
|
|
1400
963
|
*/
|
|
1401
964
|
tiers?: AchievementTier[];
|
|
1402
|
-
[key: string]: unknown;
|
|
1403
965
|
};
|
|
1404
966
|
/**
|
|
1405
967
|
* One thing a player can be shown to have done.
|
|
@@ -1450,7 +1012,6 @@ type Achievement = {
|
|
|
1450
1012
|
secret?: boolean;
|
|
1451
1013
|
/** Present makes this measured rather than flagged. See `AchievementProgress`. */
|
|
1452
1014
|
progress?: AchievementProgress;
|
|
1453
|
-
[key: string]: unknown;
|
|
1454
1015
|
};
|
|
1455
1016
|
/**
|
|
1456
1017
|
* The achievements this game keeps for a player.
|
|
@@ -1479,7 +1040,6 @@ type AchievementDeclaration = {
|
|
|
1479
1040
|
* @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be lowercase letters, digits, dots, hyphens and underscores' }
|
|
1480
1041
|
*/
|
|
1481
1042
|
list: Record<string, Achievement>;
|
|
1482
|
-
[key: string]: unknown;
|
|
1483
1043
|
};
|
|
1484
1044
|
/**
|
|
1485
1045
|
* A device a game can be played with, as one word.
|
|
@@ -1577,7 +1137,115 @@ type InputAction = {
|
|
|
1577
1137
|
* @errorMessage must not be blank
|
|
1578
1138
|
*/
|
|
1579
1139
|
label?: string;
|
|
1580
|
-
|
|
1140
|
+
};
|
|
1141
|
+
/**
|
|
1142
|
+
* What presses a player's action on a pad or on the screen: a pad button, or
|
|
1143
|
+
* an on-screen control. Keys and mouse buttons are a keyboard set's, in
|
|
1144
|
+
* `PlayerInputDeclaration.keyboard`.
|
|
1145
|
+
*
|
|
1146
|
+
* @group Input
|
|
1147
|
+
* @pattern ^(?: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))$
|
|
1148
|
+
* @errorMessage must be a pad button or an on-screen control: a player's keys and mouse buttons go in input.player.keyboard
|
|
1149
|
+
*/
|
|
1150
|
+
type PlayerBinding = string;
|
|
1151
|
+
/**
|
|
1152
|
+
* What presses a player's action on their part of the keyboard: a key, or a
|
|
1153
|
+
* mouse button.
|
|
1154
|
+
*
|
|
1155
|
+
* @group Input
|
|
1156
|
+
* @pattern ^(?:key\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|mouse\.(?:left|middle|right))$
|
|
1157
|
+
* @errorMessage must be a key or a mouse button (and Escape is the host's key)
|
|
1158
|
+
*/
|
|
1159
|
+
type KeyboardBinding = string;
|
|
1160
|
+
/**
|
|
1161
|
+
* One thing each player does: the same action for every local player, pressed
|
|
1162
|
+
* by their own pad, their own part of the keyboard, or the on-screen controls.
|
|
1163
|
+
*
|
|
1164
|
+
* @group Input
|
|
1165
|
+
*/
|
|
1166
|
+
type PlayerInputAction = {
|
|
1167
|
+
/**
|
|
1168
|
+
* What presses it on a pad, the same for every pad, and on the screen.
|
|
1169
|
+
* Leave it out for an action pressed only on the keyboard.
|
|
1170
|
+
*
|
|
1171
|
+
* @minItems 1
|
|
1172
|
+
* @maxItems 8
|
|
1173
|
+
*/
|
|
1174
|
+
bind?: PlayerBinding[];
|
|
1175
|
+
/**
|
|
1176
|
+
* What to call it in front of a player, in a remapping screen.
|
|
1177
|
+
*
|
|
1178
|
+
* @pattern \S
|
|
1179
|
+
* @maxLength 40
|
|
1180
|
+
* @errorMessage must not be blank
|
|
1181
|
+
*/
|
|
1182
|
+
title?: string;
|
|
1183
|
+
/**
|
|
1184
|
+
* What to draw on an on-screen button: a glyph, or a letter.
|
|
1185
|
+
*
|
|
1186
|
+
* @pattern \S
|
|
1187
|
+
* @maxLength 8
|
|
1188
|
+
* @errorMessage must not be blank
|
|
1189
|
+
*/
|
|
1190
|
+
label?: string;
|
|
1191
|
+
/**
|
|
1192
|
+
* Whether pressing it on a pad or a part of the keyboard nobody plays with
|
|
1193
|
+
* makes a new player, while the game is taking players. Left out on every
|
|
1194
|
+
* action, any of them does.
|
|
1195
|
+
*/
|
|
1196
|
+
join?: boolean;
|
|
1197
|
+
};
|
|
1198
|
+
/**
|
|
1199
|
+
* One part of the keyboard one player plays with: the keys and mouse buttons
|
|
1200
|
+
* that press each of the player actions there.
|
|
1201
|
+
*
|
|
1202
|
+
* @group Input
|
|
1203
|
+
*/
|
|
1204
|
+
type KeyboardSet = {
|
|
1205
|
+
/**
|
|
1206
|
+
* What the players call it: *Left keys*, *Arrows*.
|
|
1207
|
+
*
|
|
1208
|
+
* @pattern \S
|
|
1209
|
+
* @maxLength 40
|
|
1210
|
+
* @errorMessage must not be blank
|
|
1211
|
+
*/
|
|
1212
|
+
title?: string;
|
|
1213
|
+
/**
|
|
1214
|
+
* The keys and mouse buttons of each player action, by its id.
|
|
1215
|
+
*
|
|
1216
|
+
* @maxProperties 64
|
|
1217
|
+
* @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be a player action id' }
|
|
1218
|
+
*/
|
|
1219
|
+
bind: Record<string, KeyboardBinding[]>;
|
|
1220
|
+
};
|
|
1221
|
+
/**
|
|
1222
|
+
* What each local player does, and how they share the keyboard.
|
|
1223
|
+
*
|
|
1224
|
+
* **One set of actions for every player.** Each local player has their own
|
|
1225
|
+
* copy, pressed by the pad they hold, the part of the keyboard they play with,
|
|
1226
|
+
* or the on-screen controls. A pad's buttons are the same for every pad, so any
|
|
1227
|
+
* number of pads just works; the keyboard is split into sets, one player each,
|
|
1228
|
+
* so the number of sets is how many can share it.
|
|
1229
|
+
*
|
|
1230
|
+
* @group Input
|
|
1231
|
+
*/
|
|
1232
|
+
type PlayerInputDeclaration = {
|
|
1233
|
+
/**
|
|
1234
|
+
* The actions each player has, by id: never an id `actions` declares too.
|
|
1235
|
+
*
|
|
1236
|
+
* @maxProperties 32
|
|
1237
|
+
* @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be lowercase letters, digits, dots, hyphens and underscores' }
|
|
1238
|
+
*/
|
|
1239
|
+
actions: Record<string, PlayerInputAction>;
|
|
1240
|
+
/**
|
|
1241
|
+
* The parts of the keyboard players play with, one player each, in the
|
|
1242
|
+
* order they are offered. A player alone hears every one of them. Left out,
|
|
1243
|
+
* the keyboard presses no player's action.
|
|
1244
|
+
*
|
|
1245
|
+
* @minItems 1
|
|
1246
|
+
* @maxItems 4
|
|
1247
|
+
*/
|
|
1248
|
+
keyboard?: KeyboardSet[];
|
|
1581
1249
|
};
|
|
1582
1250
|
/**
|
|
1583
1251
|
* Where on the screen a group of on-screen controls sits.
|
|
@@ -1641,7 +1309,20 @@ type ControlSlot = {
|
|
|
1641
1309
|
as?: InputStick | Record<string, InputBinding$1>;
|
|
1642
1310
|
/** `thumbstick` only. See `ControlAxis`. Absent, the stick moves both ways. */
|
|
1643
1311
|
axis?: ControlAxis;
|
|
1644
|
-
|
|
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;
|
|
1645
1326
|
};
|
|
1646
1327
|
/**
|
|
1647
1328
|
* The one direction an on-screen thumbstick moves in: `x` across, `y` up and
|
|
@@ -1674,8 +1355,8 @@ type ControlZoneSlots = Record<string, ControlSlot>;
|
|
|
1674
1355
|
* A game may declare several and switch between them — a menu wants different
|
|
1675
1356
|
* controls from play, and a driving section different ones again — which is why
|
|
1676
1357
|
* they are named rather than there being one. A zone nobody knows is a layout
|
|
1677
|
-
* from a newer
|
|
1678
|
-
*
|
|
1358
|
+
* from a newer minor, which a host reads leniently, so whoever draws it simply
|
|
1359
|
+
* has nowhere to put it.
|
|
1679
1360
|
*
|
|
1680
1361
|
* @group Input
|
|
1681
1362
|
*/
|
|
@@ -1683,7 +1364,6 @@ type ControlLayout = {
|
|
|
1683
1364
|
left?: ControlZoneSlots;
|
|
1684
1365
|
right?: ControlZoneSlots;
|
|
1685
1366
|
full?: ControlZoneSlots;
|
|
1686
|
-
[key: string]: unknown;
|
|
1687
1367
|
};
|
|
1688
1368
|
/**
|
|
1689
1369
|
* What a game says about how it is played.
|
|
@@ -1733,20 +1413,99 @@ type InputDeclaration = {
|
|
|
1733
1413
|
* @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be a layout name of lowercase letters, digits, dots, hyphens and underscores' }
|
|
1734
1414
|
*/
|
|
1735
1415
|
controls?: Record<string, ControlLayout>;
|
|
1736
|
-
|
|
1416
|
+
/**
|
|
1417
|
+
* What each local player does, beside `actions`, which are everybody's. See
|
|
1418
|
+
* `PlayerInputDeclaration`.
|
|
1419
|
+
*/
|
|
1420
|
+
player?: PlayerInputDeclaration;
|
|
1421
|
+
};
|
|
1422
|
+
/**
|
|
1423
|
+
* How many people play a game on one device.
|
|
1424
|
+
*
|
|
1425
|
+
* Read by hosts before the game runs: a listing says *1–4 on one device*, and
|
|
1426
|
+
* the input panel offers a row for each player.
|
|
1427
|
+
*
|
|
1428
|
+
* @group Input
|
|
1429
|
+
* @category Useful
|
|
1430
|
+
*/
|
|
1431
|
+
type PlayersDeclaration = {
|
|
1432
|
+
/**
|
|
1433
|
+
* The most players on one device, each with their own copy of
|
|
1434
|
+
* `input.player`'s actions. 1 when left out.
|
|
1435
|
+
*
|
|
1436
|
+
* @minimum 1
|
|
1437
|
+
* @maximum 8
|
|
1438
|
+
* @asType integer
|
|
1439
|
+
*/
|
|
1440
|
+
local?: number;
|
|
1737
1441
|
};
|
|
1738
1442
|
/**
|
|
1739
|
-
*
|
|
1740
|
-
*
|
|
1741
|
-
*
|
|
1742
|
-
*
|
|
1443
|
+
* A preview's revision: a whole number from 1, the `R` of its package's
|
|
1444
|
+
* `0.R.p`. A breaking change to the preview is a new revision.
|
|
1445
|
+
*
|
|
1446
|
+
* @group Manifest
|
|
1447
|
+
* @category Useful
|
|
1448
|
+
* @minimum 1
|
|
1449
|
+
* @asType integer
|
|
1450
|
+
*/
|
|
1451
|
+
type PreviewRevision = number;
|
|
1452
|
+
/**
|
|
1453
|
+
* The previews a game is written against, each at the revision it needs.
|
|
1454
|
+
*
|
|
1455
|
+
* A preview is a part of the SDK that is not stable yet: its own `0.x`
|
|
1456
|
+
* package, versioned apart from `api`, and served only to the games that
|
|
1457
|
+
* declare it, at the revision they declare. A change to a preview reaches the
|
|
1458
|
+
* games written against it and nobody else.
|
|
1459
|
+
*
|
|
1460
|
+
* @group Manifest
|
|
1461
|
+
* @category Useful
|
|
1462
|
+
*/
|
|
1463
|
+
type PreviewsDeclaration = {
|
|
1464
|
+
/**
|
|
1465
|
+
* That this game plays over the network, in matches, at this revision of
|
|
1466
|
+
* `@hopple/sdk-preview-network`: `sdk.match`.
|
|
1467
|
+
*
|
|
1468
|
+
* The permission a reviewer grants, and what lets a host offer *Play with
|
|
1469
|
+
* friends* before the game has loaded. Nothing else about networking is
|
|
1470
|
+
* declared: the way a match is played, and its size, are chosen by the game
|
|
1471
|
+
* when it makes one. Absent, a game is never told about a match, and never
|
|
1472
|
+
* reaches one.
|
|
1473
|
+
*/
|
|
1474
|
+
network?: PreviewRevision;
|
|
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';
|
|
1493
|
+
/**
|
|
1494
|
+
* Publishing refuses a key the manifest's API does not know; a host reading a
|
|
1495
|
+
* published one ignores it (see `validateManifest`).
|
|
1743
1496
|
*
|
|
1744
1497
|
* @group Manifest
|
|
1745
1498
|
* @category Important
|
|
1746
1499
|
*/
|
|
1747
1500
|
type GameManifest = {
|
|
1748
|
-
/**
|
|
1749
|
-
|
|
1501
|
+
/**
|
|
1502
|
+
* The API the game is written against. The major picks what the game is
|
|
1503
|
+
* served; the minor is the least it needs. The CLI fills it from the
|
|
1504
|
+
* installed `@hopple/sdk` when the config leaves it out.
|
|
1505
|
+
*
|
|
1506
|
+
* @category Important
|
|
1507
|
+
*/
|
|
1508
|
+
api: ApiVersion;
|
|
1750
1509
|
/**
|
|
1751
1510
|
* Stable across versions, and unique among its publisher's games: it is the
|
|
1752
1511
|
* second half of the address the game is published at, `owner/id`, and the
|
|
@@ -1770,6 +1529,15 @@ type GameManifest = {
|
|
|
1770
1529
|
* @errorMessage must be a handle: lowercase letters, digits and hyphens
|
|
1771
1530
|
*/
|
|
1772
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;
|
|
1773
1541
|
/**
|
|
1774
1542
|
* The game's own version, `MAJOR.MINOR.PATCH`. Ours to read, never to parse for meaning.
|
|
1775
1543
|
*
|
|
@@ -1794,6 +1562,18 @@ type GameManifest = {
|
|
|
1794
1562
|
* @errorMessage must not be blank
|
|
1795
1563
|
*/
|
|
1796
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[];
|
|
1797
1577
|
/**
|
|
1798
1578
|
* A picture of the game, shown wherever a link to it is pasted — a chat, a
|
|
1799
1579
|
* post, a message.
|
|
@@ -1811,22 +1591,6 @@ type GameManifest = {
|
|
|
1811
1591
|
cover?: string;
|
|
1812
1592
|
/** @category Important */
|
|
1813
1593
|
orientation: Orientation;
|
|
1814
|
-
/**
|
|
1815
|
-
* The `@hopple/sdk` version the game was built against, optionally prefixed
|
|
1816
|
-
* with `^` or `~`.
|
|
1817
|
-
*
|
|
1818
|
-
* Recorded rather than enforced: what the host and the game actually have to
|
|
1819
|
-
* agree on is the protocol version, which the handshake settles. This is for
|
|
1820
|
-
* diagnostics — "every game that broke was built against 2.3" is a question
|
|
1821
|
-
* worth being able to ask. Deliberately far narrower than everything npm
|
|
1822
|
-
* accepts: a grammar we can read in one line is a grammar three packages can
|
|
1823
|
-
* agree on.
|
|
1824
|
-
*
|
|
1825
|
-
* @category Important
|
|
1826
|
-
* @pattern ^[\^~]?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$
|
|
1827
|
-
* @errorMessage must be a version like 1.0.0, ^1.0.0 or ~1.0.0
|
|
1828
|
-
*/
|
|
1829
|
-
sdk: string;
|
|
1830
1594
|
/**
|
|
1831
1595
|
* How the game wants the host's furniture handled. See `InsetMode`.
|
|
1832
1596
|
*
|
|
@@ -1863,6 +1627,12 @@ type GameManifest = {
|
|
|
1863
1627
|
* @category Useful
|
|
1864
1628
|
*/
|
|
1865
1629
|
input?: InputDeclaration;
|
|
1630
|
+
/**
|
|
1631
|
+
* How many play on one device. See `PlayersDeclaration`.
|
|
1632
|
+
*
|
|
1633
|
+
* @category Useful
|
|
1634
|
+
*/
|
|
1635
|
+
players?: PlayersDeclaration;
|
|
1866
1636
|
/**
|
|
1867
1637
|
* What this game makes noise with, one channel at a time, or `null` for a
|
|
1868
1638
|
* game that makes none. See `AudioDeclaration`.
|
|
@@ -1870,7 +1640,13 @@ type GameManifest = {
|
|
|
1870
1640
|
* @category Important
|
|
1871
1641
|
*/
|
|
1872
1642
|
audio: AudioDeclaration | null;
|
|
1873
|
-
|
|
1643
|
+
/**
|
|
1644
|
+
* The previews this game is written against, each at its revision. See
|
|
1645
|
+
* `PreviewsDeclaration`.
|
|
1646
|
+
*
|
|
1647
|
+
* @category Useful
|
|
1648
|
+
*/
|
|
1649
|
+
previews?: PreviewsDeclaration;
|
|
1874
1650
|
};
|
|
1875
1651
|
/**
|
|
1876
1652
|
* A game project's hopple config: `hopple.config.ts`, `hopple.config.js` or
|
|
@@ -1888,7 +1664,7 @@ type GameManifest = {
|
|
|
1888
1664
|
* export default {
|
|
1889
1665
|
* build: 'npm run build',
|
|
1890
1666
|
* page: 'dist/index.html',
|
|
1891
|
-
* manifest: {
|
|
1667
|
+
* manifest: { id: 'my-game', version: '0.1.0', title: 'My Game', orientation: 'portrait', audio: null },
|
|
1892
1668
|
* } satisfies HoppleConfig;
|
|
1893
1669
|
* ```
|
|
1894
1670
|
*
|
|
@@ -1908,18 +1684,29 @@ type HoppleConfig = {
|
|
|
1908
1684
|
*/
|
|
1909
1685
|
build?: string;
|
|
1910
1686
|
/**
|
|
1911
|
-
* The built page, relative to the config
|
|
1912
|
-
*
|
|
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.
|
|
1913
1690
|
*
|
|
1914
1691
|
* @pattern \.[Hh][Tt][Mm][Ll]?$
|
|
1915
1692
|
* @errorMessage must be the built .html file, like "dist/index.html"
|
|
1916
1693
|
*/
|
|
1917
1694
|
page: string;
|
|
1918
1695
|
/** What the game is. Uploaded beside the page, and read by every host before the game runs. */
|
|
1919
|
-
manifest:
|
|
1696
|
+
manifest: ProjectManifest;
|
|
1697
|
+
};
|
|
1698
|
+
/**
|
|
1699
|
+
* The manifest as a project's config holds it: `api` may be left out, for the
|
|
1700
|
+
* CLI to fill from the installed `@hopple/sdk`.
|
|
1701
|
+
*
|
|
1702
|
+
* @group Manifest
|
|
1703
|
+
* @category Useful
|
|
1704
|
+
*/
|
|
1705
|
+
type ProjectManifest = Omit<GameManifest, 'api'> & {
|
|
1706
|
+
api?: ApiVersion;
|
|
1920
1707
|
};
|
|
1921
1708
|
//#endregion
|
|
1922
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1709
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/achievements.d.ts
|
|
1923
1710
|
/**
|
|
1924
1711
|
* Where this player stands on one achievement.
|
|
1925
1712
|
*
|
|
@@ -2017,7 +1804,7 @@ type Achievements<Id extends string = string, Measured extends string = Id> = {
|
|
|
2017
1804
|
progress(id: Measured, value: number): void;
|
|
2018
1805
|
};
|
|
2019
1806
|
//#endregion
|
|
2020
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1807
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/analytics.d.ts
|
|
2021
1808
|
/**
|
|
2022
1809
|
* @group Analytics
|
|
2023
1810
|
* @category Important
|
|
@@ -2034,7 +1821,7 @@ type Analytics<Events extends object = Record<string, AnalyticsProps>> = {
|
|
|
2034
1821
|
track<Event extends keyof Events & string>(name: Event, props?: Events[Event]): void;
|
|
2035
1822
|
};
|
|
2036
1823
|
//#endregion
|
|
2037
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1824
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/audio.d.ts
|
|
2038
1825
|
/**
|
|
2039
1826
|
* Something about the game's sound changed: the host's order, the player's
|
|
2040
1827
|
* mute, a fader. Read what you need — `volume(channel)`, `allowed` — and apply
|
|
@@ -2066,8 +1853,6 @@ type Audio<Channel extends string = string> = {
|
|
|
2066
1853
|
readonly muted: boolean;
|
|
2067
1854
|
/** The fader over the whole game, `0`–`1`, ignoring mute. */
|
|
2068
1855
|
readonly master: number;
|
|
2069
|
-
/** The whole of what the host last said. Frozen; a new object on every change. */
|
|
2070
|
-
readonly state: AudioState;
|
|
2071
1856
|
/**
|
|
2072
1857
|
* How loud one of this game's channels should actually come out, `0`–`1`.
|
|
2073
1858
|
*
|
|
@@ -2127,7 +1912,7 @@ type Audio<Channel extends string = string> = {
|
|
|
2127
1912
|
media(element: HTMLMediaElement, channel?: Channel): Unsubscribe;
|
|
2128
1913
|
};
|
|
2129
1914
|
//#endregion
|
|
2130
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1915
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/info.d.ts
|
|
2131
1916
|
/**
|
|
2132
1917
|
* What the host said, frozen.
|
|
2133
1918
|
*
|
|
@@ -2145,7 +1930,7 @@ type Info = Readonly<{
|
|
|
2145
1930
|
device: Readonly<DeviceInfo>;
|
|
2146
1931
|
}>;
|
|
2147
1932
|
//#endregion
|
|
2148
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
1933
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/layout.d.ts
|
|
2149
1934
|
/**
|
|
2150
1935
|
* How much of each edge belongs to the host. CSS pixels.
|
|
2151
1936
|
*
|
|
@@ -2255,7 +2040,7 @@ type InsetPolicy = {
|
|
|
2255
2040
|
*/
|
|
2256
2041
|
declare function insetsFrom(obstructions: readonly Obstruction[], frame: Size, policy?: InsetPolicy): Insets;
|
|
2257
2042
|
//#endregion
|
|
2258
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2043
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/names.d.ts
|
|
2259
2044
|
/**
|
|
2260
2045
|
* The keys of a declared map, or `string` where the map is not a literal one —
|
|
2261
2046
|
* which is what a manifest typed as the wide `GameManifest` gives — and
|
|
@@ -2274,6 +2059,19 @@ type ActionName<M> = M extends {
|
|
|
2274
2059
|
actions?: infer A;
|
|
2275
2060
|
};
|
|
2276
2061
|
} ? DeclaredNames<NonNullable<A>> : never;
|
|
2062
|
+
/**
|
|
2063
|
+
* An action each local player has, as the manifest declares it under
|
|
2064
|
+
* `input.player.actions`.
|
|
2065
|
+
*
|
|
2066
|
+
* @group Names
|
|
2067
|
+
*/
|
|
2068
|
+
type PlayerActionName<M> = M extends {
|
|
2069
|
+
input?: {
|
|
2070
|
+
player?: {
|
|
2071
|
+
actions?: infer A;
|
|
2072
|
+
};
|
|
2073
|
+
};
|
|
2074
|
+
} ? DeclaredNames<NonNullable<A>> : never;
|
|
2277
2075
|
/**
|
|
2278
2076
|
* An on-screen control layout the manifest declares under `input.controls`.
|
|
2279
2077
|
*
|
|
@@ -2362,7 +2160,7 @@ type EventProps<M> = M extends {
|
|
|
2362
2160
|
*/
|
|
2363
2161
|
type StickName = 'pad.left.stick' | 'pad.right.stick' | 'pad.stick' | `controls.${string}`;
|
|
2364
2162
|
//#endregion
|
|
2365
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2163
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/state.d.ts
|
|
2366
2164
|
/**
|
|
2367
2165
|
* Where the pointer is, in the frame's own coordinates.
|
|
2368
2166
|
*
|
|
@@ -2423,246 +2221,36 @@ type InputSnapshot<Action extends string = string> = {
|
|
|
2423
2221
|
pointer: PointerState | null;
|
|
2424
2222
|
};
|
|
2425
2223
|
//#endregion
|
|
2426
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2427
|
-
/**
|
|
2428
|
-
* The name a host leaves its input shim under.
|
|
2429
|
-
*
|
|
2430
|
-
* Beside `SHIM` in spirit and separate from it in fact: that one is a channel a
|
|
2431
|
-
* platform must provide for a game to speak at all, and this one is an opinion
|
|
2432
|
-
* about drawing that most hosts will not have. A game with no shim draws the
|
|
2433
|
-
* default controls and is completely playable — which is what an embed on
|
|
2434
|
-
* somebody else's page gets, since there is nowhere for anyone to inject one.
|
|
2435
|
-
*
|
|
2436
|
-
* @group Input
|
|
2437
|
-
*/
|
|
2438
|
-
declare const INPUT_SHIM = "__HOPPLE_INPUT__";
|
|
2439
|
-
/**
|
|
2440
|
-
* One control, as a renderer is given it. Resolved: no ids left to look up.
|
|
2441
|
-
*
|
|
2442
|
-
* @group Input
|
|
2443
|
-
*/
|
|
2444
|
-
type ControlModelSlot = {
|
|
2445
|
-
id: string;
|
|
2446
|
-
type: 'buttons' | 'thumbstick';
|
|
2447
|
-
/** Always a shape this SDK knows. An unfamiliar one has already become `column`. */
|
|
2448
|
-
shape: InputLayoutShape;
|
|
2449
|
-
/**
|
|
2450
|
-
* For `buttons`: what each one presses, what to draw on it, and the standard
|
|
2451
|
-
* control it stands in for where the layout said one.
|
|
2452
|
-
*/
|
|
2453
|
-
buttons: readonly {
|
|
2454
|
-
action: string;
|
|
2455
|
-
label: string;
|
|
2456
|
-
as?: string;
|
|
2457
|
-
}[];
|
|
2458
|
-
/** For `thumbstick`: the stick id it drives. */
|
|
2459
|
-
stick: string;
|
|
2460
|
-
/**
|
|
2461
|
-
* For `thumbstick`: the one axis it moves along, where the layout held it to
|
|
2462
|
-
* one. Absent, it moves both ways. A renderer that draws it freely anyway is
|
|
2463
|
-
* still correct: the SDK zeroes the other axis of whatever is reported.
|
|
2464
|
-
*/
|
|
2465
|
-
axis?: InputLayoutAxis;
|
|
2466
|
-
};
|
|
2224
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/players.d.ts
|
|
2467
2225
|
/**
|
|
2468
|
-
*
|
|
2469
|
-
|
|
2470
|
-
type ControlModelZone = {
|
|
2471
|
-
zone: InputLayoutZone;
|
|
2472
|
-
controls: readonly ControlModelSlot[];
|
|
2473
|
-
};
|
|
2474
|
-
/**
|
|
2475
|
-
* A whole layout, resolved against the manifest's actions.
|
|
2226
|
+
* What one player holds: a pad, one part of the keyboard, or the on-screen
|
|
2227
|
+
* controls.
|
|
2476
2228
|
*
|
|
2477
2229
|
* @group Input
|
|
2478
2230
|
*/
|
|
2479
|
-
type
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
* Whoever fills this in owes that filter; `index.ts` pays it.
|
|
2493
|
-
*
|
|
2494
|
-
* Zero is the ordinary answer and always a safe one: a host that reserved the
|
|
2495
|
-
* frame instead of drawing over it has already shrunk `frame` clear of its
|
|
2496
|
-
* own furniture, and a host too old to say anything has said nothing. Both
|
|
2497
|
-
* land here as four zeroes and the pad sits where it always sat.
|
|
2498
|
-
*/
|
|
2499
|
-
insets: Insets;
|
|
2500
|
-
/**
|
|
2501
|
-
* How a thumbstick meets a thumb, as the player set it on their host.
|
|
2502
|
-
*
|
|
2503
|
-
* `floating`: marked where the layout puts it, so a player can see there is
|
|
2504
|
-
* a stick, brought to wherever a thumb lands on its side of the screen, so
|
|
2505
|
-
* the place they first touch is the centre, and pulled along by a thumb that
|
|
2506
|
-
* goes past its rim. `fixed`: moved only from where it is drawn. A renderer
|
|
2507
|
-
* that ignores this draws `fixed`, which is still a stick a game can be
|
|
2508
|
-
* played with.
|
|
2509
|
-
*/
|
|
2510
|
-
sticks: InputSticks;
|
|
2511
|
-
};
|
|
2512
|
-
/**
|
|
2513
|
-
* How a renderer says something happened.
|
|
2514
|
-
*
|
|
2515
|
-
* The only way out of a renderer, replaced or not. A shim reports and the SDK
|
|
2516
|
-
* decides: that is what keeps a replaced renderer from being able to leave an
|
|
2517
|
-
* action held forever.
|
|
2518
|
-
*
|
|
2519
|
-
* @group Input
|
|
2520
|
-
*/
|
|
2521
|
-
type ControlReport = {
|
|
2522
|
-
/** `index` is into the control's own `buttons`. */
|
|
2523
|
-
press(control: string, index: number, down: boolean): void;
|
|
2524
|
-
/** A stick's position, each axis in −1..1 and already inside the unit circle. */
|
|
2525
|
-
move(control: string, x: number, y: number): void;
|
|
2526
|
-
};
|
|
2527
|
-
/**
|
|
2528
|
-
* What a host may install to change how controls are drawn.
|
|
2529
|
-
*
|
|
2530
|
-
* **Every hook is optional and every one is wrapped.** A shim that throws is
|
|
2531
|
-
* dropped back to the default for that hook and reported to `onError`, because
|
|
2532
|
-
* a replaceable renderer is otherwise a way for one host to ship a broken pad to
|
|
2533
|
-
* every game it runs at once.
|
|
2534
|
-
*
|
|
2535
|
-
* **The SDK declares the version and the shim adapts.** This file is frozen
|
|
2536
|
-
* inside bundles that shipped months before any given shim was written, so the
|
|
2537
|
-
* dangerous direction is a new shim meeting an old SDK — and it is safe by
|
|
2538
|
-
* construction if the shim is the side doing the adapting. A shim reads
|
|
2539
|
-
* `request.contract` and answers with what that version can carry.
|
|
2540
|
-
*
|
|
2541
|
-
* @group Input
|
|
2542
|
-
*/
|
|
2543
|
-
type InputShim = {
|
|
2544
|
-
/** The contract version this shim was written against. `1` today. */
|
|
2545
|
-
v: 1;
|
|
2546
|
-
/**
|
|
2547
|
-
* Whether to draw controls for this layout at all.
|
|
2548
|
-
*
|
|
2549
|
-
* The host's half of the answer; the game's half is having asked. Returning
|
|
2550
|
-
* nothing leaves the decision alone, which is what a shim that only wanted to
|
|
2551
|
-
* restyle should do.
|
|
2552
|
-
*/
|
|
2553
|
-
decide?(request: {
|
|
2554
|
-
layout: string;
|
|
2555
|
-
frame: Size;
|
|
2556
|
-
/** What the host has taken along each edge — see `ControlModel.insets`. */
|
|
2557
|
-
insets: Insets;
|
|
2558
|
-
contract: number;
|
|
2559
|
-
}): boolean | undefined;
|
|
2560
|
-
/**
|
|
2561
|
-
* How far a stick must move before it counts, 0 to 1.
|
|
2562
|
-
*
|
|
2563
|
-
* The host's rather than the game's, because it is an accessibility setting
|
|
2564
|
-
* before it is a taste: a player whose hands shake needs a wider dead region
|
|
2565
|
-
* in every game they open, not in the ones that happened to think of it.
|
|
2566
|
-
*/
|
|
2567
|
-
deadzone?: number;
|
|
2568
|
-
/** A tick when a control is pressed. Absent where the platform has none. */
|
|
2569
|
-
haptic?(kind: 'press' | 'release'): void;
|
|
2570
|
-
/** Called with the root once, to set custom properties or add a stylesheet. */
|
|
2571
|
-
style?(root: ShadowRoot, request: {
|
|
2572
|
-
layout: string;
|
|
2573
|
-
frame: Size;
|
|
2574
|
-
}): void;
|
|
2575
|
-
/**
|
|
2576
|
-
* Draw the controls instead of the SDK.
|
|
2577
|
-
*
|
|
2578
|
-
* Given the root, the resolved model and the one way back. Returns a teardown,
|
|
2579
|
-
* which is called before the next render and when the controls are hidden.
|
|
2580
|
-
*/
|
|
2581
|
-
render?(root: ShadowRoot, model: ControlModel, report: ControlReport): (() => void) | void;
|
|
2231
|
+
type Controller =
|
|
2232
|
+
/** One of the keyboard sets the game declared, from 0, with the title it gave it. */
|
|
2233
|
+
{
|
|
2234
|
+
kind: 'keyboard';
|
|
2235
|
+
set: number;
|
|
2236
|
+
title: string;
|
|
2237
|
+
} |
|
|
2238
|
+
/** A pad, by `Gamepad.index`. */
|
|
2239
|
+
{
|
|
2240
|
+
kind: 'gamepad';
|
|
2241
|
+
pad: number;
|
|
2242
|
+
} | {
|
|
2243
|
+
kind: 'touch';
|
|
2582
2244
|
};
|
|
2583
|
-
/**
|
|
2584
|
-
* @group Input
|
|
2585
|
-
*/
|
|
2586
|
-
declare function isInputShim(value: unknown): value is InputShim;
|
|
2587
|
-
/**
|
|
2588
|
-
* The contract version this SDK speaks. Told to a shim so it can answer in kind.
|
|
2589
|
-
*
|
|
2590
|
-
* `2` added `insets` — to the model a renderer is given, to what `decide` is
|
|
2591
|
-
* asked, and as four custom properties the default sheet spends. Purely
|
|
2592
|
-
* additive: a `v: 1` shim that reads none of them behaves exactly as it did.
|
|
2593
|
-
*
|
|
2594
|
-
* `3` added `axis` to a thumbstick in the model. Additive again: a shim that
|
|
2595
|
-
* draws the stick moving both ways still gives the game one axis, because the
|
|
2596
|
-
* other is dropped from what it reports.
|
|
2597
|
-
*
|
|
2598
|
-
* `4` added `sticks` to the model: whether the player wants a thumbstick that
|
|
2599
|
-
* comes to their thumb. Additive: a shim that reads nothing of it draws the
|
|
2600
|
-
* stick where it always did.
|
|
2601
|
-
*
|
|
2602
|
-
* @group Input
|
|
2603
|
-
*/
|
|
2604
|
-
declare const INPUT_CONTRACT = 4;
|
|
2605
|
-
//#endregion
|
|
2606
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-DAYaix/game/src/input/bindings.d.ts
|
|
2607
|
-
/**
|
|
2608
|
-
* The standard mapping's buttons, by index, as the names this SDK uses.
|
|
2609
|
-
*
|
|
2610
|
-
* `Gamepad.mapping === 'standard'` is what makes these indices mean anything;
|
|
2611
|
-
* a pad that reports anything else is read for its axes and ignored for its
|
|
2612
|
-
* buttons, because an unmapped index is a number with no meaning attached.
|
|
2613
|
-
*
|
|
2614
|
-
* **The left cluster is the d-pad and the right is the faces**, which is what
|
|
2615
|
-
* makes `pad.south` cover both *A* and *down* with one word. Compass positions
|
|
2616
|
-
* rather than letters throughout: A and B are swapped between the two
|
|
2617
|
-
* best-selling controllers, so a name built on them is wrong for half the
|
|
2618
|
-
* players who read it.
|
|
2619
|
-
*
|
|
2620
|
-
* @group Input
|
|
2621
|
-
*/
|
|
2622
|
-
declare const PAD_BUTTONS: readonly (string | null)[];
|
|
2623
|
-
/**
|
|
2624
|
-
* The binding a player would actually use, given what they are holding.
|
|
2625
|
-
*
|
|
2626
|
-
* A game that draws one prompt for an action with four bindings has to be told
|
|
2627
|
-
* which one, and the answer is the one their hands are on. Falls back to the
|
|
2628
|
-
* first declared binding, which is the game author's own idea of the primary
|
|
2629
|
-
* one.
|
|
2630
|
-
*
|
|
2631
|
-
* @group Input
|
|
2632
|
-
* @category Useful
|
|
2633
|
-
*/
|
|
2634
|
-
declare function preferredBinding(bind: readonly string[], scheme: InputScheme): string | undefined;
|
|
2635
|
-
/**
|
|
2636
|
-
* Whether a binding is one the player could press with what they are using.
|
|
2637
|
-
*
|
|
2638
|
-
* @group Input
|
|
2639
|
-
* @category Useful
|
|
2640
|
-
*/
|
|
2641
|
-
declare function servesScheme(binding: string, scheme: InputScheme): boolean;
|
|
2642
|
-
/**
|
|
2643
|
-
* What to write on the screen for one binding.
|
|
2644
|
-
*
|
|
2645
|
-
* **Best effort, and the host is allowed to do better.** Whether the south face
|
|
2646
|
-
* button says *A* or *B* depends on whose controller it is, and
|
|
2647
|
-
* `Gamepad.id` is free text that varies by browser and by operating system — so
|
|
2648
|
-
* a host that genuinely knows, because it is a native app, sends its own label
|
|
2649
|
-
* and this is what fills in when nobody did. Getting it slightly wrong is a
|
|
2650
|
-
* prompt a player can still follow; refusing to draw one is a game that never
|
|
2651
|
-
* says which button to press.
|
|
2652
|
-
*
|
|
2653
|
-
* @group Input
|
|
2654
|
-
* @category Useful
|
|
2655
|
-
*/
|
|
2656
|
-
declare function labelFor(binding: string, scheme: InputScheme): string;
|
|
2657
2245
|
//#endregion
|
|
2658
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2246
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/input/index.d.ts
|
|
2659
2247
|
/**
|
|
2660
|
-
*
|
|
2248
|
+
* A binding a game can name on its own, for a prompt about a button that is
|
|
2249
|
+
* not one of its actions: `pad.start`, `key.Space`.
|
|
2661
2250
|
*
|
|
2662
2251
|
* @group Input
|
|
2663
|
-
* @category Useful
|
|
2664
2252
|
*/
|
|
2665
|
-
|
|
2253
|
+
type ButtonName = `key.${string}` | `pad.${string}` | `mouse.${string}` | `pointer.${string}` | `touch.${string}`;
|
|
2666
2254
|
/**
|
|
2667
2255
|
* What presses each action, and what the player is pressing with: the part of
|
|
2668
2256
|
* input that is state rather than a stream of presses.
|
|
@@ -2673,11 +2261,12 @@ type InputState<Action extends string = string> = {
|
|
|
2673
2261
|
/**
|
|
2674
2262
|
* What presses each action, and what to call it in front of a player.
|
|
2675
2263
|
*
|
|
2676
|
-
* `mapping.shoot.label`
|
|
2677
|
-
*
|
|
2678
|
-
*
|
|
2679
|
-
*
|
|
2680
|
-
*
|
|
2264
|
+
* `mapping.shoot.label` names one binding in text: *Z* on a keyboard, a
|
|
2265
|
+
* letter for whatever pad the player picked up, and the label the manifest
|
|
2266
|
+
* gave the on-screen button on a touch screen. A prompt is better drawn with
|
|
2267
|
+
* `glyph`, which shows every binding, the same on every controller, and
|
|
2268
|
+
* follows a rebinding without being redrawn. An action can be missing, so
|
|
2269
|
+
* keep a fallback.
|
|
2681
2270
|
*/
|
|
2682
2271
|
readonly mapping: Readonly<Partial<Record<Action, InputBinding>>>;
|
|
2683
2272
|
/** What the player is using right now. */
|
|
@@ -2697,7 +2286,7 @@ type InputListener<Action extends string = string> = (now: InputState<Action>, p
|
|
|
2697
2286
|
* @group Input
|
|
2698
2287
|
* @category Important
|
|
2699
2288
|
*/
|
|
2700
|
-
type Input<Action extends string = string, Layout extends string = string> = InputState<Action> & {
|
|
2289
|
+
type Input<Action extends string = string, Layout extends string = string, PlayerAction extends string = Action> = InputState<Action> & {
|
|
2701
2290
|
/**
|
|
2702
2291
|
* Run this whenever an action goes down or comes up.
|
|
2703
2292
|
*
|
|
@@ -2737,6 +2326,49 @@ type Input<Action extends string = string, Layout extends string = string> = Inp
|
|
|
2737
2326
|
* The pointer's position needs `poll` set; presses and sticks do not.
|
|
2738
2327
|
*/
|
|
2739
2328
|
read(): InputSnapshot<Action>;
|
|
2329
|
+
/**
|
|
2330
|
+
* A character that draws what presses an action now, in the font `font`
|
|
2331
|
+
* names: every key or button bound to it, for the device in the player's
|
|
2332
|
+
* hands, as a keycap or a pad button.
|
|
2333
|
+
*
|
|
2334
|
+
* hint.textContent = `Press ${hopple.input.glyph('jump')} to jump`;
|
|
2335
|
+
* ctx.font = `20px 'My Font', sans-serif, ${hopple.input.font}`;
|
|
2336
|
+
*
|
|
2337
|
+
* The character is the same for the whole session; what changes is the font
|
|
2338
|
+
* the SDK draws it with, which follows the player's bindings and their
|
|
2339
|
+
* device. So text in the DOM is right without being touched again, and a
|
|
2340
|
+
* canvas is right the next time it is painted. A binding works too —
|
|
2341
|
+
* `glyph('pad.start')` — for a button that is no action of the game's.
|
|
2342
|
+
*
|
|
2343
|
+
* Only in a font stack that names `font`, and names it last: a box
|
|
2344
|
+
* otherwise. Under `hopple dev` a glyph printed anywhere else is reported to
|
|
2345
|
+
* `onError`.
|
|
2346
|
+
*/
|
|
2347
|
+
glyph(name: Action | ButtonName): string;
|
|
2348
|
+
/**
|
|
2349
|
+
* What `glyph` draws, in words: `Z or Space`, `bottom face button`. For an
|
|
2350
|
+
* `aria-label`, and for text drawn with no fonts at all. Empty for an action
|
|
2351
|
+
* with nothing bound for this device.
|
|
2352
|
+
*/
|
|
2353
|
+
describe(name: Action | ButtonName): string;
|
|
2354
|
+
/** The font family `glyph` draws in: name it, last, wherever a glyph is printed. */
|
|
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;
|
|
2740
2372
|
/**
|
|
2741
2373
|
* Stop reading input and let go of everything held.
|
|
2742
2374
|
*
|
|
@@ -2748,6 +2380,63 @@ type Input<Action extends string = string, Layout extends string = string> = Inp
|
|
|
2748
2380
|
suspend(): void;
|
|
2749
2381
|
resume(): void;
|
|
2750
2382
|
readonly suspended: boolean;
|
|
2383
|
+
/**
|
|
2384
|
+
* The players on this device, each with their own copy of the actions
|
|
2385
|
+
* `input.player` declares, by `slot`. Empty for a game that declares none;
|
|
2386
|
+
* otherwise the first player is always there.
|
|
2387
|
+
*
|
|
2388
|
+
* Frozen and replaced when somebody joins or leaves. `read()`, `onAction`
|
|
2389
|
+
* and `glyph` on the input itself answer for a player action as if every
|
|
2390
|
+
* player pressed it: what a menu anybody may work reads.
|
|
2391
|
+
*/
|
|
2392
|
+
readonly players: readonly Player<PlayerAction>[];
|
|
2393
|
+
/** Run this when a player joins or leaves, claims a controller, or picks up another. */
|
|
2394
|
+
onPlayers(listener: (players: readonly Player<PlayerAction>[]) => void): Unsubscribe;
|
|
2395
|
+
/**
|
|
2396
|
+
* Whether a press of a join action on a controller nobody plays with seats
|
|
2397
|
+
* a new player: set it on the screen where players join, and clear it
|
|
2398
|
+
* before play.
|
|
2399
|
+
*
|
|
2400
|
+
* Setting it gives the first player the controller they last used, so the
|
|
2401
|
+
* others' presses on the rest make them players. The first player's first
|
|
2402
|
+
* press, if they have pressed nothing yet, is theirs.
|
|
2403
|
+
*/
|
|
2404
|
+
joining: boolean;
|
|
2405
|
+
/**
|
|
2406
|
+
* The most players this device may have now: what the manifest declares, or
|
|
2407
|
+
* fewer where the match being played has no room for more.
|
|
2408
|
+
*/
|
|
2409
|
+
readonly mostPlayers: number;
|
|
2410
|
+
};
|
|
2411
|
+
/**
|
|
2412
|
+
* One player on this device: their own actions, read from the controllers
|
|
2413
|
+
* they hold.
|
|
2414
|
+
*
|
|
2415
|
+
* @group Input
|
|
2416
|
+
* @category Important
|
|
2417
|
+
*/
|
|
2418
|
+
type Player<Action extends string = string> = {
|
|
2419
|
+
/** Their place, from 0: the lowest free one when they joined, kept until they leave. */
|
|
2420
|
+
readonly slot: number;
|
|
2421
|
+
/**
|
|
2422
|
+
* What they play with: the controller they claimed, or the one they last
|
|
2423
|
+
* used. `null` once their pad has gone away, until a free one is pressed.
|
|
2424
|
+
*/
|
|
2425
|
+
readonly controller: Controller | null;
|
|
2426
|
+
/** What they are using now, for what to draw: never which device to read. */
|
|
2427
|
+
readonly scheme: InputScheme;
|
|
2428
|
+
/** What presses each action for them, on the controllers they hear. */
|
|
2429
|
+
readonly mapping: Readonly<Partial<Record<Action, InputBinding>>>;
|
|
2430
|
+
/** Their actions now, and what happened since their last read: once a frame, as `input.read()`. */
|
|
2431
|
+
read(): InputSnapshot<Action>;
|
|
2432
|
+
onAction(action: Action, listener: ActionListener<Action>): Unsubscribe;
|
|
2433
|
+
/** Their sticks: their pad's, or the on-screen one when they hold the touch screen. */
|
|
2434
|
+
onStick(stick: StickName, listener: StickListener): Unsubscribe;
|
|
2435
|
+
/** A character drawing what presses an action for this player, as `input.glyph` draws it. */
|
|
2436
|
+
glyph(action: Action | ButtonName): string;
|
|
2437
|
+
describe(action: Action | ButtonName): string;
|
|
2438
|
+
/** Take them out: what they held is free again. The only player stays. */
|
|
2439
|
+
leave(): void;
|
|
2751
2440
|
};
|
|
2752
2441
|
/**
|
|
2753
2442
|
* The on-screen controls: shown when the game says so *and* the host says they
|
|
@@ -2791,7 +2480,7 @@ type Controls<Layout extends string = string> = {
|
|
|
2791
2480
|
readonly available: boolean;
|
|
2792
2481
|
};
|
|
2793
2482
|
//#endregion
|
|
2794
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2483
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/lifecycle.d.ts
|
|
2795
2484
|
/** @group Lifecycle */
|
|
2796
2485
|
type LifecycleListener = (phase: Phase, previous: Phase) => void | Promise<void>;
|
|
2797
2486
|
/**
|
|
@@ -2810,7 +2499,7 @@ type Lifecycle = {
|
|
|
2810
2499
|
on(listener: LifecycleListener): Unsubscribe;
|
|
2811
2500
|
};
|
|
2812
2501
|
//#endregion
|
|
2813
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2502
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/loop.d.ts
|
|
2814
2503
|
/**
|
|
2815
2504
|
* The host has let the game run its loop, or stopped it. Return a promise to
|
|
2816
2505
|
* make the host wait.
|
|
@@ -2864,7 +2553,36 @@ type Loop = {
|
|
|
2864
2553
|
tick(frame: FrameCallback): Unsubscribe;
|
|
2865
2554
|
};
|
|
2866
2555
|
//#endregion
|
|
2867
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2556
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/panel.d.ts
|
|
2557
|
+
/**
|
|
2558
|
+
* The pages of the host's panel a game has a name for, as the keys of this
|
|
2559
|
+
* interface.
|
|
2560
|
+
*
|
|
2561
|
+
* An interface rather than a union so that a preview can add its own pages to
|
|
2562
|
+
* it, as it adds its own namespace to `GameSdk`: a page only a preview's games
|
|
2563
|
+
* are offered is named only where that preview is installed.
|
|
2564
|
+
*
|
|
2565
|
+
* @group Panels
|
|
2566
|
+
*/
|
|
2567
|
+
interface PanelPages {
|
|
2568
|
+
/** What this game is: its title, its author, its description. */
|
|
2569
|
+
about: true;
|
|
2570
|
+
/** Who has scored what, on this game's boards. */
|
|
2571
|
+
scores: true;
|
|
2572
|
+
/** What has been unlocked, and how far along the rest is. */
|
|
2573
|
+
achievements: true;
|
|
2574
|
+
/** What this game is played with, and where a player rebinds it. */
|
|
2575
|
+
input: true;
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* A page of the host's panel: one of `PanelPages`.
|
|
2579
|
+
*
|
|
2580
|
+
* It grows, and it only ever grows: a name means the same thing for ever,
|
|
2581
|
+
* because a game ships and is never updated.
|
|
2582
|
+
*
|
|
2583
|
+
* @group Panels
|
|
2584
|
+
*/
|
|
2585
|
+
type PageId = keyof PanelPages;
|
|
2868
2586
|
/**
|
|
2869
2587
|
* Where the host's panel stands over the game.
|
|
2870
2588
|
*
|
|
@@ -2973,7 +2691,7 @@ type Panel = {
|
|
|
2973
2691
|
open(page: PageId): void;
|
|
2974
2692
|
};
|
|
2975
2693
|
//#endregion
|
|
2976
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2694
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/pause.d.ts
|
|
2977
2695
|
/**
|
|
2978
2696
|
* The host has asked for a pause, or for the game to carry on.
|
|
2979
2697
|
*
|
|
@@ -3034,7 +2752,7 @@ type Pause = {
|
|
|
3034
2752
|
on(listener: PauseListener): Unsubscribe;
|
|
3035
2753
|
};
|
|
3036
2754
|
//#endregion
|
|
3037
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2755
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/resources.d.ts
|
|
3038
2756
|
/**
|
|
3039
2757
|
* The host has let the game hold its resources again, or asked for them back.
|
|
3040
2758
|
* Return a promise to make the host wait until they have gone, or come back.
|
|
@@ -3064,7 +2782,7 @@ type Resources = {
|
|
|
3064
2782
|
on(listener: ResourcesListener): Unsubscribe;
|
|
3065
2783
|
};
|
|
3066
2784
|
//#endregion
|
|
3067
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2785
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/save.d.ts
|
|
3068
2786
|
/**
|
|
3069
2787
|
* What a game gets back from a read: its own schema number, and its state.
|
|
3070
2788
|
*
|
|
@@ -3103,6 +2821,45 @@ type SaveStatus = {
|
|
|
3103
2821
|
* @category Useful
|
|
3104
2822
|
*/
|
|
3105
2823
|
type SaveListener = (now: SaveStatus, previous: SaveStatus) => void;
|
|
2824
|
+
/**
|
|
2825
|
+
* Make one save of two that went their own ways.
|
|
2826
|
+
*
|
|
2827
|
+
* `mine` is this device's, `theirs` one another device made meanwhile, and
|
|
2828
|
+
* neither was built on the other. Answer with the save to keep in place of
|
|
2829
|
+
* both, on whatever schema `version` it is written in. Throw where the two
|
|
2830
|
+
* cannot be merged — a schema this build does not know — and the host asks the
|
|
2831
|
+
* player which to keep.
|
|
2832
|
+
*
|
|
2833
|
+
* Pure: it is called with two states and must not touch the game's own. Run
|
|
2834
|
+
* while the game loads, its answer is what `read()` resolves with; run while
|
|
2835
|
+
* the game plays, its answer goes to `onMerge` as well.
|
|
2836
|
+
*
|
|
2837
|
+
* @group Save
|
|
2838
|
+
* @category Useful
|
|
2839
|
+
*/
|
|
2840
|
+
type SaveMerger = (mine: SaveState, theirs: SaveState) => SaveState;
|
|
2841
|
+
/**
|
|
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
|
+
*
|
|
2845
|
+
* @group Save
|
|
2846
|
+
* @category Useful
|
|
2847
|
+
*/
|
|
2848
|
+
type SaveMergeListener = (merged: SaveState) => void;
|
|
2849
|
+
/**
|
|
2850
|
+
* What a game says about its save at `init`, before the host has asked
|
|
2851
|
+
* anything.
|
|
2852
|
+
*
|
|
2853
|
+
* @group Save
|
|
2854
|
+
* @category Useful
|
|
2855
|
+
*/
|
|
2856
|
+
type SaveSetup = {
|
|
2857
|
+
/**
|
|
2858
|
+
* How to make one save of two. Left out, the host asks the player which of
|
|
2859
|
+
* the two to keep. See `SaveMerger`.
|
|
2860
|
+
*/
|
|
2861
|
+
merge?: SaveMerger | undefined;
|
|
2862
|
+
};
|
|
3106
2863
|
/**
|
|
3107
2864
|
* @group Save
|
|
3108
2865
|
* @category Important
|
|
@@ -3149,11 +2906,20 @@ type Save = {
|
|
|
3149
2906
|
}): Promise<void>;
|
|
3150
2907
|
/** Throw the save away — a game resetting its own progress, at a player's asking. */
|
|
3151
2908
|
clear(): Promise<void>;
|
|
2909
|
+
/**
|
|
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.
|
|
2912
|
+
*
|
|
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.
|
|
2916
|
+
*/
|
|
2917
|
+
onMerge(listener: SaveMergeListener): Unsubscribe;
|
|
3152
2918
|
/** The largest save this host will take, in bytes. `SAVE_BYTES` where it said nothing. */
|
|
3153
2919
|
readonly maxBytes: number;
|
|
3154
2920
|
};
|
|
3155
2921
|
//#endregion
|
|
3156
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2922
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/score.d.ts
|
|
3157
2923
|
/**
|
|
3158
2924
|
* Where this player stands on every board, as far as anybody has said.
|
|
3159
2925
|
*
|
|
@@ -3230,7 +2996,7 @@ type Score<Board extends string = string> = {
|
|
|
3230
2996
|
}): Promise<ScoreResult>;
|
|
3231
2997
|
};
|
|
3232
2998
|
//#endregion
|
|
3233
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
2999
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/share.d.ts
|
|
3234
3000
|
/**
|
|
3235
3001
|
* @group Share
|
|
3236
3002
|
* @category Important
|
|
@@ -3302,7 +3068,7 @@ type Share = {
|
|
|
3302
3068
|
}): void;
|
|
3303
3069
|
};
|
|
3304
3070
|
//#endregion
|
|
3305
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-
|
|
3071
|
+
//#region ../../../../../../../tmp/hopple-sdk-types-A6KFyv/game/src/init.d.ts
|
|
3306
3072
|
/**
|
|
3307
3073
|
* What `init` hands back: every namespace a game has, on one object.
|
|
3308
3074
|
*
|
|
@@ -3317,7 +3083,7 @@ type Share = {
|
|
|
3317
3083
|
* @group Bridge
|
|
3318
3084
|
* @category Useful
|
|
3319
3085
|
*/
|
|
3320
|
-
|
|
3086
|
+
interface GameSdk<M extends object = GameManifest> {
|
|
3321
3087
|
/**
|
|
3322
3088
|
* How far the game has got: `loading`, `ready`, `disposed`. The one thing
|
|
3323
3089
|
* here a game must actually call is `lifecycle.ready()`.
|
|
@@ -3433,7 +3199,7 @@ type GameSdk<M extends object = GameManifest> = {
|
|
|
3433
3199
|
*
|
|
3434
3200
|
* @category Useful
|
|
3435
3201
|
*/
|
|
3436
|
-
input: Input<ActionName<M>, LayoutName<M>>;
|
|
3202
|
+
input: Input<ActionName<M> | PlayerActionName<M>, LayoutName<M>, PlayerActionName<M>>;
|
|
3437
3203
|
/**
|
|
3438
3204
|
* How loud this game may be, which the host decides and the game obeys.
|
|
3439
3205
|
*
|
|
@@ -3526,19 +3292,17 @@ type GameSdk<M extends object = GameManifest> = {
|
|
|
3526
3292
|
*
|
|
3527
3293
|
* Resolves with the round trip in milliseconds, rejecting with a `BridgeError`
|
|
3528
3294
|
* whose code is `timeout` if the host never answers. This is the liveness
|
|
3529
|
-
* check
|
|
3295
|
+
* check, and what a game can measure the bridge with.
|
|
3530
3296
|
*/
|
|
3531
3297
|
ping(): Promise<number>;
|
|
3532
3298
|
/** Tear the bridge down. Calls in flight reject with `disposed`. */
|
|
3533
3299
|
dispose(): void;
|
|
3534
|
-
}
|
|
3300
|
+
}
|
|
3535
3301
|
/**
|
|
3536
3302
|
* @group Bridge
|
|
3537
3303
|
* @category Useful
|
|
3538
3304
|
*/
|
|
3539
3305
|
type InitOptions = {
|
|
3540
|
-
/** The window the game is running in. Defaults to the global one. */
|
|
3541
|
-
scope?: Window | undefined;
|
|
3542
3306
|
/**
|
|
3543
3307
|
* How long a call waits before rejecting with `timeout`.
|
|
3544
3308
|
*
|
|
@@ -3548,22 +3312,6 @@ type InitOptions = {
|
|
|
3548
3312
|
* in it.
|
|
3549
3313
|
*/
|
|
3550
3314
|
timeoutMs?: number | undefined;
|
|
3551
|
-
/**
|
|
3552
|
-
* The channel to talk over. Defaults to whatever the game turns out to be
|
|
3553
|
-
* running in — a shim a native shell left, or the frame that embedded us —
|
|
3554
|
-
* which is what every real game wants; a test, or a host that embeds a game
|
|
3555
|
-
* some other way, can hand over its own.
|
|
3556
|
-
*/
|
|
3557
|
-
transport?: Transport | undefined;
|
|
3558
|
-
/**
|
|
3559
|
-
* How long to wait for a host to turn up when there is neither a shim nor a
|
|
3560
|
-
* parent frame, before rejecting. Defaults to `SHIM_WAIT_MS`.
|
|
3561
|
-
*
|
|
3562
|
-
* Here from day one rather than when a platform first needs it: this is
|
|
3563
|
-
* game-side code, bundled into a game at build time, so it can never be added
|
|
3564
|
-
* later for games already published.
|
|
3565
|
-
*/
|
|
3566
|
-
shimWaitMs?: number | undefined;
|
|
3567
3315
|
/**
|
|
3568
3316
|
* Where a failure that belongs to nobody's promise goes — a lifecycle
|
|
3569
3317
|
* listener that threw, an event the SDK refused. Defaults to `console.error`.
|
|
@@ -3572,6 +3320,15 @@ type InitOptions = {
|
|
|
3572
3320
|
* them, as it is about any error the game throws and nothing catches.
|
|
3573
3321
|
*/
|
|
3574
3322
|
onError?: ((error: unknown) => void) | undefined;
|
|
3323
|
+
/**
|
|
3324
|
+
* What the game says about its save before the host asks anything: how to
|
|
3325
|
+
* merge two saves that went their own ways, on two devices. See `SaveSetup`.
|
|
3326
|
+
*
|
|
3327
|
+
* Here rather than on `sdk.save`, because the host may ask while the game is
|
|
3328
|
+
* still reading its save — the merge is what that read resolves with — and a
|
|
3329
|
+
* merge set after the read would be one the host never heard of.
|
|
3330
|
+
*/
|
|
3331
|
+
save?: SaveSetup | undefined;
|
|
3575
3332
|
};
|
|
3576
3333
|
/**
|
|
3577
3334
|
* Introduce the game to its host, and hand back the SDK once it has answered.
|
|
@@ -3607,62 +3364,4 @@ type InitOptions = {
|
|
|
3607
3364
|
*/
|
|
3608
3365
|
declare function init<M extends object = GameManifest>(options?: InitOptions): Promise<GameSdk<M>>;
|
|
3609
3366
|
//#endregion
|
|
3610
|
-
|
|
3611
|
-
/**
|
|
3612
|
-
* How long a game with no shim and no parent waits for one to turn up.
|
|
3613
|
-
*
|
|
3614
|
-
* Generous on purpose: a shim two seconds late is not coming, and the only game
|
|
3615
|
-
* made to wait is one that was never going to run.
|
|
3616
|
-
*
|
|
3617
|
-
* @group Bridge
|
|
3618
|
-
*/
|
|
3619
|
-
declare const SHIM_WAIT_MS = 2000;
|
|
3620
|
-
/** @group Bridge */
|
|
3621
|
-
type HostTransportOptions = {
|
|
3622
|
-
/** The window the game is running in. Defaults to the global one. */
|
|
3623
|
-
scope?: Window | undefined;
|
|
3624
|
-
/** How long to wait for a late shim before giving up. */
|
|
3625
|
-
shimWaitMs?: number | undefined;
|
|
3626
|
-
/** Where a shim that arrived wrongly, or twice, is reported. */
|
|
3627
|
-
onError?: ((error: unknown) => void) | undefined;
|
|
3628
|
-
};
|
|
3629
|
-
/**
|
|
3630
|
-
* Find something to talk over, waiting briefly if there is nothing yet.
|
|
3631
|
-
*
|
|
3632
|
-
* Asynchronous because of that last case alone. The two ordinary ones — a shim
|
|
3633
|
-
* already installed, a parent frame — resolve without yielding a turn.
|
|
3634
|
-
*
|
|
3635
|
-
* @group Bridge
|
|
3636
|
-
* @category Useful
|
|
3637
|
-
*/
|
|
3638
|
-
declare function hostTransport(options?: HostTransportOptions): Promise<Transport>;
|
|
3639
|
-
//#endregion
|
|
3640
|
-
//#region ../../../../../../../tmp/hopple-sdk-types-DAYaix/game/src/shim.d.ts
|
|
3641
|
-
/**
|
|
3642
|
-
* Whether an arbitrary value is a shim this SDK knows how to speak to.
|
|
3643
|
-
*
|
|
3644
|
-
* A version we do not know is a shim we must not guess at — a `v: 2` that moved
|
|
3645
|
-
* `send` to a different signature would be a channel we corrupt rather than one
|
|
3646
|
-
* we fail on. Refusing it falls through to the frame branch, and then to the
|
|
3647
|
-
* wait, which either connects or says clearly that nothing did.
|
|
3648
|
-
*
|
|
3649
|
-
* @group Bridge
|
|
3650
|
-
*/
|
|
3651
|
-
declare function isShim(value: unknown): value is HoppleShim;
|
|
3652
|
-
/**
|
|
3653
|
-
* What a platform left for us, or nothing — in which case we are in a frame.
|
|
3654
|
-
*
|
|
3655
|
-
* @group Bridge
|
|
3656
|
-
*/
|
|
3657
|
-
declare function findShim(scope: Window): HoppleShim | null;
|
|
3658
|
-
/**
|
|
3659
|
-
* A shim, seen by the peer as an ordinary channel.
|
|
3660
|
-
*
|
|
3661
|
-
* The trust boundary is in here, in SDK code, and never in the platform's: a
|
|
3662
|
-
* shim that cannot validate is a shim that cannot validate wrongly.
|
|
3663
|
-
*
|
|
3664
|
-
* @group Bridge
|
|
3665
|
-
*/
|
|
3666
|
-
declare function shimTransport(shim: HoppleShim): Transport;
|
|
3667
|
-
//#endregion
|
|
3668
|
-
export { ACHIEVEMENT_VALUE_MAX, AUDIO_FULL, AUDIO_MASTER, AUDIO_VOLUME_MAX, type Achievement, type AchievementDeclaration, type AchievementListener, type AchievementName, type AchievementProgress, type AchievementResult, 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 AppInfo, type AppSurface, type Audio, type AudioChannel, type AudioDeclaration, type AudioListener, type AudioState, type BoardName, BridgeError, type ChannelName, type ControlLayout, type ControlModel, type ControlModelSlot, type ControlModelZone, type ControlReport, type ControlShape, type ControlSlot, type ControlZone, type Controls, DEADZONE, type DeclaredNames, type DeclaredValue, type DeviceInfo, type DeviceTier, type ErrorCode, type EventName, type EventProps, type FrameCallback, type GameInfo, type GameManifest, type GameSdk, type HoppleConfig, type HoppleShim, type HostTransportOptions, INPUT_CONTRACT, INPUT_SCHEMES, INPUT_SHIM, type Info, type InitOptions, type Input, type InputAction, type InputBinding, type InputChangeCause, type InputDeclaration, type InputDevice, type InputListener, type InputScheme, type InputShim, 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, MANIFEST_FILE, MANIFEST_SCHEMA, MAX_STEP, type MeasuredName, type Obstruction, type ObstructionKind, type Orientation, PAD_BUTTONS, PAGES, PAUSE_NONE, PAUSE_STATES, type PageId, type Panel, type PanelListener, type PanelState, type Pause, type PauseListener, type PauseState, type Phase, type PlayerInfo, type PointerListener, type PointerState, type Rect, type Resources, type ResourcesListener, SAVE_BYTES, SCORE_VALUE_MAX, SET_SHIM, SHARE_BYTES, SHARE_META_BYTES, SHIM, SHIM_WAIT_MS, type Save, type SaveDeclaration, type SaveListener, 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, audioVolumeOf, findShim, hostTransport, init, insetsFrom, isInputShim, isShim, labelFor, preferredBinding, servesScheme, shimTransport };
|
|
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 };
|