@hopple/sdk 0.0.0-stage → 0.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts ADDED
@@ -0,0 +1,3664 @@
1
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/achievement.d.ts
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-XAG7tD/protocol/src/analytics.d.ts
32
+ /**
33
+ * What a game says happened.
34
+ *
35
+ * The bridge carries only this. Everything the host can see for itself — phases,
36
+ * placements, deadlines, the end — it records where the messages already are, so
37
+ * a game cannot skew it and a game written before any of this existed still
38
+ * leaves a full trace.
39
+ *
40
+ * **One event, one message, as it happens.** No buffer and no batch: a pipe
41
+ * sized for a handful of events a session gains nothing by holding them, and
42
+ * holding them would cost a flush on every way a game can end. Whoever delivers
43
+ * these to a server will batch there, where delivery is, and where the batching
44
+ * can be as clever as the delivery needs.
45
+ *
46
+ * **Nothing here is a limit.** How many events a run may carry, how long a name
47
+ * may be, how much a prop may hold: those are the host's, and every host will
48
+ * answer differently — a dev shell keeps everything, an app in the field keeps
49
+ * what its warehouse can take. What this file settles is the *shape*, which both
50
+ * ends must agree on, and the two rules the manifest is written under: what a
51
+ * name looks like, and the namespace a game may not use.
52
+ */
53
+ /**
54
+ * What a prop may hold. Flat scalars, because a column is flat.
55
+ *
56
+ * @group Analytics
57
+ */
58
+ type AnalyticsValue = string | number | boolean;
59
+ /** @group Analytics */
60
+ type AnalyticsProps = Record<string, AnalyticsValue>;
61
+ //#endregion
62
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/audio.d.ts
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
+ */
85
+ /**
86
+ * The loudest anything gets, and the quietest.
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.
93
+ *
94
+ * @group Audio
95
+ */
96
+ declare const AUDIO_VOLUME_MAX = 1;
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.
101
+ *
102
+ * @group Audio
103
+ */
104
+ declare const AUDIO_MASTER = "master";
105
+ /**
106
+ * How loud the game may be, as the host has it set right now.
107
+ *
108
+ * **Mute and the master fader are separate facts and stay separate.** A player
109
+ * who mutes a game and then unmutes it expects the volume they had, not the
110
+ * one they would have to find again — so mute is not `master: 0`, and a game
111
+ * that stored it as one would hand the player back silence.
112
+ *
113
+ * The whole state travels every time rather than a delta, for the reason
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
119
+ */
120
+ type AudioState = {
121
+ /**
122
+ * Everything off, whatever the faders say.
123
+ *
124
+ * The one control a host can count on a player reaching for in a hurry, and
125
+ * the reason it is a boolean of its own: silence now, and the mix they had
126
+ * when they come back.
127
+ */
128
+ muted: boolean;
129
+ /** The fader over all of it, `0`–`1`. */
130
+ master: number;
131
+ /**
132
+ * Each declared channel's own fader, `0`–`1`, by the id its manifest gave it.
133
+ *
134
+ * A channel missing from here is `1` — a host that has never been asked about
135
+ * `music` is not a host that wants it silent. That is what lets a game's next
136
+ * version add a channel and have it audible for every player who already had
137
+ * settings stored.
138
+ */
139
+ channels: Record<string, number>;
140
+ };
141
+ /**
142
+ * What every game gets where nobody has said otherwise: audible, and mixed the
143
+ * way the game meant it.
144
+ *
145
+ * The reading that claims the least, which for audio is the *loud* one — a game
146
+ * that started silent against a host too old to have this API would be a game
147
+ * that appeared broken to everybody, and a player who wants quiet has a control
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-XAG7tD/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
+ */
180
+ /** @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';
222
+ /** @group Bridge */
223
+ type ProtocolError = {
224
+ code: ErrorCode;
225
+ message: string;
226
+ };
227
+ /**
228
+ * What a rejected call rejects with.
229
+ *
230
+ * @group Bridge
231
+ */
232
+ declare class BridgeError extends Error {
233
+ code: ErrorCode;
234
+ constructor(error: ProtocolError);
235
+ }
236
+ //#endregion
237
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/envelope.d.ts
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-XAG7tD/protocol/src/input.d.ts
279
+ /**
280
+ * What the host says about how this game is being played.
281
+ *
282
+ * **Nothing about input is asked for over this bridge.** A key is pressed
283
+ * inside the frame and read inside the frame; a thumbstick under a thumb is
284
+ * sampled a metre from the game that wants it. Putting either on the wire would
285
+ * spend a message and a frame of latency on something already in the room.
286
+ *
287
+ * So what travels here is what the frame has no copy of. The game's own
288
+ * declaration — its actions and its layouts of on-screen controls — comes from
289
+ * the manifest, and the manifest is the host's: the frame holds none. With it
290
+ * comes the *mapping* — the declared bindings with whatever a player has
291
+ * remapped folded over them — the scheme they are using, and whether on-screen
292
+ * controls apply on this host at all. All of it is wanted before the first
293
+ * frame, and the last three can change under a running game without the game
294
+ * being able to notice.
295
+ *
296
+ * **The bindings are carried as strings and not parsed here.** Their vocabulary
297
+ * lives in `@hopple/manifest`, where a binding is first written down, and
298
+ * whoever resolves one imports it from there. A host newer than the game it is
299
+ * talking to will send bindings this game has never heard of, and the right
300
+ * thing to do with those is to keep them on the wire and drop them at the point
301
+ * of use — which is what a resolver that only matches what it knows does for
302
+ * free.
303
+ */
304
+ /**
305
+ * Which kind of input the player is using right now.
306
+ *
307
+ * The last one they actually used, not everything attached: a laptop with a
308
+ * controller plugged in is a keyboard until somebody moves a stick. What a game
309
+ * does with it is choose its prompts — *Press Z* against a glyph — and decide
310
+ * whether to draw touch hints of its own.
311
+ *
312
+ * @group Input
313
+ */
314
+ type InputScheme = 'keyboard' | 'mouse' | 'touch' | 'gamepad';
315
+ /** @group Input */
316
+ declare const INPUT_SCHEMES: readonly InputScheme[];
317
+ /**
318
+ * What presses one action, and what to call it in front of a player.
319
+ *
320
+ * `bind` is the effective list — the manifest's, with a remap folded over it —
321
+ * in the order a game should prefer them when it draws only one.
322
+ *
323
+ * **`label` is the host's, and it is why this is on the wire at all.** The same
324
+ * action is *Z* on a keyboard, *A* on one controller and *B* on another, and
325
+ * only the host knows which the player is holding. A game writes
326
+ * `Press ${mapping.shoot.label}` and is right everywhere without ever learning
327
+ * what a controller is. Absent means the host had nothing better to say than the
328
+ * game's own declaration.
329
+ *
330
+ * @group Input
331
+ */
332
+ type InputBinding = {
333
+ bind: string[];
334
+ /** What to call the action itself. From the manifest unless the host knows better. */
335
+ title?: string;
336
+ /** What to show for the binding a player would actually use. See above. */
337
+ label?: string;
338
+ };
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-XAG7tD/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
+ //#endregion
419
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/pause.d.ts
420
+ /**
421
+ * Whether the game is holding still, which the host may ask for and only the
422
+ * game can answer.
423
+ *
424
+ * **This is the third shape a namespace on this bridge takes, and it is worth
425
+ * naming.** A save, a score and a panel are services: the game asks and the
426
+ * host may refuse. Audio runs the other way: the host states and the game
427
+ * obeys, with no refusal to model. Pausing is neither. The host asks, and the
428
+ * game is the only side that knows whether there is anything to stop — so the
429
+ * answer is not an acknowledgement but a *state*, and one of the three it may
430
+ * be is "you cannot ask me that just now".
431
+ *
432
+ * **Pausing is not an order, and the two must not be folded together.** The
433
+ * orders in `Allowed` are what the *host* lets the game run — a loop stopped
434
+ * because the game is being swiped past is the host's fact, declared rather
435
+ * than negotiated. This is the player's fact: they pressed a button, and the
436
+ * game stopped its clock and put a menu up. A host asks for a pause as it takes
437
+ * a game in play away from the player, and leaves resuming to the player: one
438
+ * that muddled the two would un-pause a player's game by scrolling back to it.
439
+ *
440
+ * **Whether a game can be paused at all is nothing more than what it says.** A
441
+ * game that never announces a state is `unpausable` for ever and is never
442
+ * asked, which is what every game published before this existed gets. There is
443
+ * no separate capability to declare: "cannot be paused" and "nothing to stop
444
+ * just now" are one fact to a player, and one state here.
445
+ *
446
+ * **`unpausable` is a real state and not an error.** A game between rounds, on
447
+ * its title screen, or — in the demo — with nothing on the field to stop, has
448
+ * nothing to pause; saying so is more useful than refusing a call, because it
449
+ * is a fact the host draws rather than one it recovers from. It also moves on
450
+ * its own, which is why the game announces the state as well as answering
451
+ * about it.
452
+ */
453
+ /**
454
+ * Where the game stands, as one word.
455
+ *
456
+ * Three values rather than a boolean and a capability flag, because the third
457
+ * one is what a host actually draws around: a control that is *absent* is a
458
+ * different thing from one that is showing "Pause", and a host holding two
459
+ * booleans has to work out which of their four combinations mean nothing.
460
+ *
461
+ * @group Pause
462
+ */
463
+ type PauseState =
464
+ /** Playing. A host draws Pause. */
465
+ 'running' |
466
+ /** Held, at the player's asking. A host draws Resume. */
467
+ 'paused' |
468
+ /**
469
+ * There is nothing here to stop right now.
470
+ *
471
+ * A title screen, the gap between rounds, a cut scene that has to finish.
472
+ * A host draws nothing at all — not a disabled button, which is a promise
473
+ * that pressing it would do something.
474
+ */
475
+ '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
+ //#endregion
492
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/save.d.ts
493
+ /**
494
+ * The one thing a game may keep.
495
+ *
496
+ * A game reads its state while it loads and writes it as it plays. Where that
497
+ * lands is the host's — `localStorage` in the dev shell today, a row behind an
498
+ * account later — and what is in it is the game's, which the host never parses.
499
+ *
500
+ * **The shape of this is not the shape of analytics**, and every difference is
501
+ * deliberate. Analytics is one-way and unacknowledged because nothing waits for
502
+ * it; a read's answer is the whole point, a write has to be known to have
503
+ * landed, and a save that quietly went missing is something a player feels. So
504
+ * these are requests, they have error codes, and the game is told when one
505
+ * failed.
506
+ *
507
+ * **Nothing here is a limit either.** How big a save a host will take, how often
508
+ * it will take one, whether it debounces and how long its window is: all the
509
+ * host's, all revisable on the host's own deploy cadence. What this file settles
510
+ * is the *shape* both ends must agree on, and one floor — `SAVE_BYTES` — that a
511
+ * game may assume anywhere, forever, because a game ships and is never updated.
512
+ */
513
+ /**
514
+ * What a game may write anywhere, in bytes of `state` as UTF-8.
515
+ *
516
+ * The floor rather than the cap: a host that will take more says so in the
517
+ * welcome, and a game that never asks still knows this much is safe. 64 KiB is
518
+ * far more than a save of positions and counters needs and far less than any
519
+ * store would notice.
520
+ *
521
+ * @group Save
522
+ */
523
+ declare const SAVE_BYTES = 65536;
524
+ /**
525
+ * What is waiting, without fetching it.
526
+ *
527
+ * Small, fixed-size, and already known to any host that has a store, so it
528
+ * rides in the `Welcome` where the blob deliberately does not. It is what tells
529
+ * a game whether the round trip is worth making: a game draws *Continue* or
530
+ * *New game* before it has asked for anything, and a game now on schema 3 that
531
+ * will not migrate a 1 never fetches it at all.
532
+ *
533
+ * @group Save
534
+ */
535
+ type SaveStored = {
536
+ /** The host's clock, when the record was written. */
537
+ at: number;
538
+ version: number;
539
+ /** How big `state` is, so a game knows what it is about to be handed. */
540
+ bytes: number;
541
+ };
542
+ //#endregion
543
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/score.d.ts
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;
582
+ /**
583
+ * What the host says came of it.
584
+ *
585
+ * `improved` and `best` are answerable by any host that took the submission,
586
+ * because they are about this player's own history and nothing else. `rank` and
587
+ * `of` are not: under a host that acknowledges when the score is *durable*
588
+ * rather than when a server has seen it, there may be no server answer yet. So
589
+ * they are optional, and a host that does not know leaves them out rather than
590
+ * guessing — a rank a game drew from a guess is a rank a player will screenshot.
591
+ *
592
+ * @group Score
593
+ */
594
+ type ScoreResult = {
595
+ /** Whether this run beat the player's previous best. Ties are not improvements. */
596
+ improved: boolean;
597
+ /** The player's best on this board, after this submission. */
598
+ best: number;
599
+ /** Where that best stands, when the host knows. */
600
+ rank?: number;
601
+ /** Out of how many players, when the host knows. */
602
+ of?: number;
603
+ };
604
+ /**
605
+ * Where one player stands on one board.
606
+ *
607
+ * @group Score
608
+ */
609
+ type ScoreStanding = {
610
+ best: number;
611
+ /** The host's clock, when the best was set. */
612
+ at: number;
613
+ /** How many runs this player has submitted to this board. */
614
+ runs: number;
615
+ rank?: number;
616
+ of?: number;
617
+ };
618
+ //#endregion
619
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/share.d.ts
620
+ /**
621
+ * A link to this game, offered to the player by the host.
622
+ *
623
+ * A game cannot share itself. It sits in a sandboxed frame on an opaque origin
624
+ * with no top-level navigation, no popups and no clipboard the player has
625
+ * gestured at — and it does not know its own address in the first place, because
626
+ * where this game lives is the host's fact and differs per host: a row in a feed
627
+ * on the site, a deep link into the app, a path on somebody else's page. So the
628
+ * game asks, and the host does the offering.
629
+ *
630
+ * **The game names no URL and is told none.** It says only what it would like
631
+ * carried along, and the host mints the link, decides how to put it in front of
632
+ * the player — a share sheet, the clipboard, a dialogue — and tells the player
633
+ * what became of it. A game that could read its own link would be a game that
634
+ * could fabricate one, and a host that took a URL from a game would be
635
+ * publishing whatever the game handed it.
636
+ *
637
+ * **The game is not told what the player did with it.** The host is the one
638
+ * that opened the sheet or wrote the clipboard, so the host is the one that
639
+ * says "link copied", and a game that forgot to would leave a player with a
640
+ * copy nobody mentioned. A game with an outcome in hand would also be a game
641
+ * that could pay for shares, which is a thing no player should be asked for.
642
+ *
643
+ * **`data` is the game's, and opaque to everyone else.** The host keeps it with
644
+ * the link and gives it back to whoever opens that link, without ever parsing
645
+ * it: a level seed, a board, a score to beat. What it means is settled between
646
+ * the game that wrote it and the same game reading it back, which is the only
647
+ * pair that can agree about it.
648
+ *
649
+ * **`meta` is the game's too, and it goes the other way: to the platform, never
650
+ * back to a game.** Why the link was made — the level, the score that was
651
+ * beaten, which of the game's own buttons was pressed — kept beside the host's
652
+ * own record of who made it and how, so the people running a game can learn
653
+ * which of its shares bring players in. The game reading a link back is never
654
+ * told it, so nothing a game plays on can depend on it.
655
+ *
656
+ * **A game asks; only the host refuses.** Nothing is negotiated at the
657
+ * handshake and there is nothing for a game to check first: whether there is a
658
+ * link to give, how often one may be asked for, how much data one will carry
659
+ * are all the host's, all revisable on the host's own deploy cadence, and all
660
+ * answered when a player actually presses the button. What this file settles is
661
+ * the shape both ends must agree on, and `SHARE_BYTES` — the amount of `data`
662
+ * every host takes, forever, because a game ships and is never updated.
663
+ *
664
+ * **Both directions, and they are not symmetrical.** Going out is a request
665
+ * the game does not wait on: the host answers as soon as it has taken the offer
666
+ * and before it opens anything, because a sheet stays up for as long as the
667
+ * player likes and no reply timeout can be long enough for that. Coming back —
668
+ * the data a link carried into this run — is a fact of the run and rides in the
669
+ * welcome beside `info`, because a game opened from a link has to know before
670
+ * its first frame. See `ShareWelcome`.
671
+ */
672
+ /**
673
+ * What every host carries, in bytes of `data` as UTF-8.
674
+ *
675
+ * The floor rather than the cap: a host may take more and none may take less,
676
+ * so a game that stays under this is never refused for its size anywhere. It is
677
+ * the one number about sharing a game can rely on without asking, which is why
678
+ * it is a constant rather than something the wire carries.
679
+ *
680
+ * Small on purpose. 512 bytes is a seed, a board, a score; a save belongs in
681
+ * `save`, which is where a host keeps things that are big. It is the number
682
+ * every game already published was promised, so it stays the floor whether a
683
+ * host carries the data in the link itself or keeps it on a server and puts
684
+ * only a reference in the link.
685
+ *
686
+ * @group Share
687
+ */
688
+ declare const SHARE_BYTES = 512;
689
+ /**
690
+ * What every host takes of `meta`, in bytes of its JSON as UTF-8.
691
+ *
692
+ * A floor like `SHARE_BYTES`, and for the same reason: a game that stays under
693
+ * it is never refused anywhere. Enough for a handful of facts about why a link
694
+ * was made; a game that wants to say more is describing its state, which is
695
+ * what `data` is for.
696
+ *
697
+ * @group Share
698
+ */
699
+ declare const SHARE_META_BYTES = 1024;
700
+ /**
701
+ * Something a game can say about a share: what JSON can say, and nothing else.
702
+ *
703
+ * @group Share
704
+ */
705
+ type ShareMetaValue = string | number | boolean | null | ShareMetaValue[] | {
706
+ [key: string]: ShareMetaValue;
707
+ };
708
+ /**
709
+ * Why a game made a link, in its own words — kept by the platform beside who
710
+ * made it and how, and never handed back to a game.
711
+ *
712
+ * @group Share
713
+ */
714
+ type ShareMeta = {
715
+ [key: string]: ShareMetaValue;
716
+ };
717
+ //#endregion
718
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/methods.d.ts
719
+ /**
720
+ * How far a game has got with its own preparation.
721
+ *
722
+ * Only ever forwards, and only ever about the game itself — not about what is
723
+ * being done with it. `disposed` ends it.
724
+ *
725
+ * booting → loading → ready → disposed
726
+ *
727
+ * @group Lifecycle
728
+ */
729
+ type Phase =
730
+ /**
731
+ * The frame exists and nothing has spoken yet: the bundle is still starting
732
+ * up. A game never observes this — there is no game running to observe it —
733
+ * but a host needs a word for the gap, because a bundle that never runs looks
734
+ * exactly like one that has not run *yet*, and only a deadline tells them
735
+ * apart.
736
+ */
737
+ 'booting' |
738
+ /** Up and talking, getting its own things together: assets, save, first frame. */
739
+ 'loading' |
740
+ /** The game says it can be shown. The one phase it declares rather than obeys. */
741
+ 'ready' |
742
+ /** Going away. The last thing a game is told, and its chance to flush. */
743
+ 'disposed';
744
+ /**
745
+ * A box in the frame's own coordinates: `0,0` is the frame's top-left corner.
746
+ *
747
+ * @group Layout
748
+ */
749
+ type Rect = {
750
+ x: number;
751
+ y: number;
752
+ width: number;
753
+ height: number;
754
+ };
755
+ /**
756
+ * How big something is, in CSS pixels.
757
+ *
758
+ * @group Layout
759
+ */
760
+ type Size = {
761
+ width: number;
762
+ height: number;
763
+ };
764
+ /**
765
+ * What is over the game, and why.
766
+ *
767
+ * A game avoids all of these the same way, so they arrive in one list rather
768
+ * than several a game would have to concatenate before it could use them.
769
+ *
770
+ * @group Layout
771
+ */
772
+ type ObstructionKind =
773
+ /** Host interface: a rail, a corner control. It takes the input there too. */
774
+ 'chrome' |
775
+ /** The device itself: a notch, a camera hole, a rounded corner. */
776
+ 'cutout' |
777
+ /** Something the host has put over the game for now: a sheet, a dialogue. */
778
+ 'overlay' |
779
+ /**
780
+ * The SDK's own on-screen controls, drawn inside the frame.
781
+ *
782
+ * The one kind a host never sends: it is synthesised where the controls are
783
+ * drawn, and joins the same list so that a game keeping clear of reserved
784
+ * rectangles keeps clear of a thumbstick without being told twice.
785
+ */
786
+ 'controls';
787
+ /** @group Layout */
788
+ type Obstruction = {
789
+ rect: Rect;
790
+ kind: ObstructionKind;
791
+ /**
792
+ * Whether this is part of the game's usable area or merely on top of it.
793
+ *
794
+ * `true` means stay out: the region is not yours, and pointer events there
795
+ * will never reach you. `false` means something is covering you for now and
796
+ * what to do about it is entirely your decision — the SDK leaves it out of
797
+ * the insets, so a game that ignores this keeps the layout it had.
798
+ *
799
+ * A separate field rather than something to be read off `kind`, because a
800
+ * game ships and is never updated: one written today will one day meet a kind
801
+ * nobody has invented yet, and this is what lets it be right about it anyway.
802
+ */
803
+ reserved: boolean;
804
+ };
805
+ /**
806
+ * Which kind of host this is.
807
+ *
808
+ * @group Info
809
+ */
810
+ type AppSurface =
811
+ /** The Hopple app, in a WebView. */
812
+ 'app' |
813
+ /** A browser: the production site, or a game embedded on someone else's page. */
814
+ 'web' |
815
+ /** A developer's own machine, under `hopple dev`. Never a player. */
816
+ 'dev';
817
+ /**
818
+ * How much machine there is, as one word.
819
+ *
820
+ * A budget the host worked out when it started the game, not a live reading —
821
+ * which is what makes it safe to say once. Anything that changes while a game
822
+ * runs is a different kind of fact and would need somewhere to change.
823
+ *
824
+ * @group Info
825
+ */
826
+ type DeviceTier = 'low' | 'mid' | 'high';
827
+ /**
828
+ * Which host is running this game.
829
+ *
830
+ * @group Info
831
+ */
832
+ type AppInfo = {
833
+ /** Who it is, as a stable name: `hopple`, or whoever embedded the game. */
834
+ id: string;
835
+ /** Their version. For a game's own diagnostics; the protocol version is what capability is settled by. */
836
+ version: string;
837
+ /**
838
+ * `dev` is the one worth acting on, and the one to be careful with: it is how
839
+ * a game shows its own frame counter to its author and to nobody else. A game
840
+ * that *plays* differently in dev is a game whose dev testing proves less than
841
+ * it looks.
842
+ */
843
+ surface: AppSurface;
844
+ };
845
+ /**
846
+ * Which game the host believes it is running.
847
+ *
848
+ * From the manifest that arrived with the verified bundle, not from anything the
849
+ * bundle says about itself. A game that inlined its own version at build time
850
+ * and finds it disagreeing with this one has been shipped wrong, and from inside
851
+ * the frame there is nowhere else that is visible.
852
+ *
853
+ * @group Info
854
+ */
855
+ type GameInfo = {
856
+ id: string;
857
+ version: string;
858
+ };
859
+ /**
860
+ * Who is playing, as far as a game is allowed to know.
861
+ *
862
+ * @group Info
863
+ */
864
+ type PlayerInfo = {
865
+ /**
866
+ * An opaque name for this player *in this game*, stable while they keep
867
+ * playing it, and `null` where the host has no notion of a player at all —
868
+ * a site running one game has none, and a game keying anything to a player
869
+ * has to survive that.
870
+ *
871
+ * **Per game, not per person.** A host derives this from the player and the
872
+ * game together, so two games by the same author cannot recognise the same
873
+ * person between them. Nothing here can enforce that — this package only
874
+ * carries what a host hands over — so it is a rule every host we write obeys.
875
+ */
876
+ id: string | null;
877
+ /** Whether this player has played *this* game before. */
878
+ returning: boolean;
879
+ /**
880
+ * The language to speak, as a BCP 47 tag.
881
+ *
882
+ * The player's, not the device's, which is why it is here rather than under
883
+ * `device`: `navigator.language` is already readable inside the frame and
884
+ * answers what the operating system is set to, while this is what they chose
885
+ * in the app.
886
+ */
887
+ locale: string;
888
+ };
889
+ /**
890
+ * What the host is running the game on.
891
+ *
892
+ * @group Info
893
+ */
894
+ type DeviceInfo = {
895
+ tier: DeviceTier;
896
+ };
897
+ //#endregion
898
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/listeners.d.ts
899
+ /**
900
+ * The one way anything here is listened to: `on(listener)` hands back the
901
+ * function that stops it.
902
+ *
903
+ * Every `on()` a game or a host is offered behaves the same, because they are
904
+ * all this:
905
+ *
906
+ * - **Each `on()` is its own.** The same function added twice is called twice,
907
+ * and each returned function takes back only the registration it came from —
908
+ * so two parts of a game that happen to share a handler cannot unsubscribe
909
+ * each other.
910
+ * - **Stopping is final and safe to repeat.** A listener taken back while
911
+ * others are being told is not called after that point, and calling the
912
+ * returned function again does nothing.
913
+ * - **A listener added while others are being told hears the next change**,
914
+ * not the one under way: it was not there when it happened.
915
+ * - **One that throws is reported and stops nobody.** The others are still
916
+ * told, and whoever made the change still gets on with it.
917
+ *
918
+ * @module listeners
919
+ */
920
+ /**
921
+ * Stops the listener an `on()` added. Calling it again does nothing.
922
+ *
923
+ * @group Bridge
924
+ */
925
+ type Unsubscribe = () => void;
926
+ //#endregion
927
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/protocol/src/transport.d.ts
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-XAG7tD/manifest/src/manifest.d.ts
1026
+ /**
1027
+ * What a game says about itself.
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.
1068
+ *
1069
+ * @group Manifest
1070
+ * @category Useful
1071
+ */
1072
+ declare const MANIFEST_SCHEMA = 1;
1073
+ /**
1074
+ * How the game wants to be held. The host decides what to do about it.
1075
+ *
1076
+ * @group Layout
1077
+ */
1078
+ type Orientation = 'portrait' | 'landscape' | 'any';
1079
+ /**
1080
+ * Whether the host may put its own furniture over the game's frame.
1081
+ *
1082
+ * The one layout question that has to be answered before the bundle runs, which
1083
+ * is why it is declared here rather than asked at runtime: the host sizes the
1084
+ * frame before there is a game to ask. A game that says nothing gets `reserve`,
1085
+ * so a rail never lands on the buttons of something written before rails
1086
+ * existed.
1087
+ *
1088
+ * @group Layout
1089
+ */
1090
+ type InsetMode =
1091
+ /** The frame is the whole surface; read `sdk.layout` and keep out of the way. */
1092
+ 'draw-under' |
1093
+ /** The host shrinks the frame clear of its own furniture. Nothing to avoid. */
1094
+ 'reserve';
1095
+ /**
1096
+ * What a game declares about layout. Absent means every default.
1097
+ *
1098
+ * @group Layout
1099
+ * @category Useful
1100
+ */
1101
+ type LayoutDeclaration = {
1102
+ insets: InsetMode;
1103
+ [key: string]: unknown;
1104
+ };
1105
+ /**
1106
+ * What a prop may hold, as one word. The three the wire carries, and no more.
1107
+ *
1108
+ * @group Analytics
1109
+ */
1110
+ type AnalyticsType = 'string' | 'number' | 'boolean';
1111
+ /**
1112
+ * One event's props. Every one optional: see `AnalyticsDeclaration`.
1113
+ *
1114
+ * Prop names follow the grammar event names do, which `@hopple/protocol`
1115
+ * enforces on the wire too; a test keeps the two in step.
1116
+ *
1117
+ * @group Analytics
1118
+ * @maxProperties 8
1119
+ * @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 24, errorMessage: 'must be a prop name of lowercase letters, digits, dots, hyphens and underscores' }
1120
+ */
1121
+ type AnalyticsShape = Record<string, AnalyticsType>;
1122
+ /**
1123
+ * The custom events a game sends, and what each one carries.
1124
+ *
1125
+ * **The first key here whose reader is not the host.** `layout.insets` is
1126
+ * declared because a frame has to be sized before there is a game to ask;
1127
+ * nothing about analytics has to be answered before the bundle runs. What earns
1128
+ * this its place is cardinality: names and columns invented at runtime make a
1129
+ * pipe nobody can build a table for, and the party that has to build that table
1130
+ * — the publish CLI, the server, a creator dashboard — is exactly the party that
1131
+ * already reads the manifest and never talks to a game.
1132
+ *
1133
+ * **Every prop is optional and nothing is required**, which is what keeps the
1134
+ * declaration cheap enough to be worth asking for: forgetting one costs a column
1135
+ * rather than the event. The additive rule carries over unchanged — add events,
1136
+ * add props, never retype one — and that is the whole of what a per-event table
1137
+ * needs to survive the game's next version.
1138
+ *
1139
+ * @group Analytics
1140
+ * @category Useful
1141
+ */
1142
+ type AnalyticsDeclaration = {
1143
+ /**
1144
+ * Every event the game may send, by name.
1145
+ *
1146
+ * Names starting `hopple.` are the host's own records and not a game's to
1147
+ * use. The grammar is the wire's too: `@hopple/protocol` enforces the same
1148
+ * one, and a test keeps the two in step. At most 64 events: the wire never
1149
+ * sees a declaration, so nothing enforces this at runtime and nothing has to.
1150
+ *
1151
+ * @maxProperties 64
1152
+ * @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
+ */
1154
+ events: Record<string, AnalyticsShape>;
1155
+ [key: string]: unknown;
1156
+ };
1157
+ /**
1158
+ * What a game expects to keep, in bytes.
1159
+ *
1160
+ * **The manifest's own test is whether the host must know before the bundle
1161
+ * runs**, and here it must: a host that prefetches a save from a server has to
1162
+ * know which games have one before there is a game to ask, and a publish
1163
+ * pipeline that provisions storage has to know before a player exists.
1164
+ *
1165
+ * The budget the game expects, not a grant. A host answers with its own cap at
1166
+ * the handshake, and a game with no key here may still call the API — it just
1167
+ * gets nothing prefetched, which is the honest reading of not having said so.
1168
+ *
1169
+ * @group Save
1170
+ * @category Useful
1171
+ */
1172
+ type SaveDeclaration = {
1173
+ /**
1174
+ * At most 1 MiB. Not the same question as what a host will take — that is
1175
+ * the host's and is settled at the handshake. This is only what is worth
1176
+ * writing down: a declaration in the megabytes is a game that has
1177
+ * misunderstood what this is for, and finding that out at publish time is
1178
+ * cheaper than at a player's.
1179
+ *
1180
+ * @asType integer
1181
+ * @exclusiveMinimum 0
1182
+ * @maximum 1048576
1183
+ */
1184
+ bytes: number;
1185
+ [key: string]: unknown;
1186
+ };
1187
+ /**
1188
+ * One thing a game makes noise with, as the player is offered it.
1189
+ *
1190
+ * The title is the whole of it, and required: this exists to be a label on a
1191
+ * fader in a host's own panel, and a fader labelled `sfx2` is a control nobody
1192
+ * can use. What a channel *is* — a bus, a set of samples, one oscillator — is
1193
+ * the game's business and is never described here.
1194
+ *
1195
+ * @group Audio
1196
+ */
1197
+ type AudioChannel = {
1198
+ /**
1199
+ * What to call it in front of a player.
1200
+ *
1201
+ * @category Important
1202
+ * @pattern \S
1203
+ * @maxLength 40
1204
+ * @errorMessage must not be blank
1205
+ */
1206
+ title: string;
1207
+ [key: string]: unknown;
1208
+ };
1209
+ /**
1210
+ * What this game makes noise with, so a host can offer a fader for each.
1211
+ *
1212
+ * **A map from the first version, even for a game with one channel.** The
1213
+ * channel id travels in `audio.set`, which is frozen game-side code, and a
1214
+ * single-channel key would have to *change meaning* to grow a second one — the
1215
+ * one move the additive rule forbids.
1216
+ *
1217
+ * **The manifest's test is whether the host must know before the bundle runs**,
1218
+ * and here it must twice over. The panel is drawn where the game's title is,
1219
+ * which a host draws for games in a feed that nobody has opened; and the
1220
+ * opening volumes ride in the handshake, so the host has to know which channels
1221
+ * it is stating a volume for before there is a game to ask.
1222
+ *
1223
+ * **The key is required, and `null` is how a silent game says so.** Leaving it
1224
+ * out would read the same as forgetting it, and a game that makes noise but
1225
+ * never said so is one whose player cannot turn it down. An object with no
1226
+ * channels is a game with sound and no tracks worth a fader of their own: it
1227
+ * gets a master fader and a mute button and no more. What is never optional is
1228
+ * obeying what the host then says: see `AudioState`.
1229
+ *
1230
+ * @group Audio
1231
+ * @category Useful
1232
+ */
1233
+ type AudioDeclaration = {
1234
+ /**
1235
+ * The channels, by id. Optional, and may be empty: either way there is only
1236
+ * the master. At most 8: a panel of faders nobody can read is not a feature.
1237
+ *
1238
+ * `master` is refused rather than quietly shadowed. A master fader exists
1239
+ * whether or not a game has said anything, and an author who wrote it meant
1240
+ * something by it; a fader that turned out to be the host's own would be a
1241
+ * control that ignored half of what they wired it to. The id grammar is the
1242
+ * wire's too, and a test keeps the two in step.
1243
+ *
1244
+ * @maxProperties 8
1245
+ * @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
+ */
1247
+ channels?: Record<string, AudioChannel>;
1248
+ [key: string]: unknown;
1249
+ };
1250
+ /**
1251
+ * Which way a board sorts — the one thing about a score that cannot be guessed.
1252
+ *
1253
+ * `desc` is the ordinary leaderboard, where more is better. `asc` is a time, a
1254
+ * stroke count, a number of moves. Nothing downstream can infer this from the
1255
+ * values it holds, and a client that guessed would render a time board upside
1256
+ * down, so it is required rather than defaulted.
1257
+ *
1258
+ * @group Score
1259
+ */
1260
+ type ScoreDirection = 'asc' | 'desc';
1261
+ /**
1262
+ * How to draw the number.
1263
+ *
1264
+ * A client renders a board for a game it has never run, so it cannot ask the
1265
+ * game what `224310` means. `integer` is a count, `decimal` a measurement,
1266
+ * `duration-ms` a time to be written as minutes and seconds. Absent is
1267
+ * `integer`, which is what most boards are.
1268
+ *
1269
+ * @group Score
1270
+ */
1271
+ type ScoreFormat = 'integer' | 'decimal' | 'duration-ms';
1272
+ /** @group Score */
1273
+ type ScoreBoard = {
1274
+ /**
1275
+ * What to call it in front of a player.
1276
+ *
1277
+ * @category Important
1278
+ * @pattern \S
1279
+ * @maxLength 40
1280
+ * @errorMessage must not be blank
1281
+ */
1282
+ title: string;
1283
+ direction: ScoreDirection;
1284
+ /** See `ScoreFormat`. Absent means `integer`. */
1285
+ format?: ScoreFormat;
1286
+ [key: string]: unknown;
1287
+ };
1288
+ /**
1289
+ * The boards this game keeps a score on.
1290
+ *
1291
+ * **A map from the first version, even for a game that wants one board.** The
1292
+ * board id travels in `score.submit`, which is frozen game-side code, and a
1293
+ * single-board key would have to *change meaning* to grow a second one — the
1294
+ * one thing the additive rule forbids. A map costs a game with one board the
1295
+ * few characters of naming it, and costs a game with four nothing at all.
1296
+ *
1297
+ * **Read by the host and by the server**, which is what earns it a key rather
1298
+ * than a runtime call: a client draws a game's board in a sheet over a feed
1299
+ * without ever having run that game, and the server has to know how a board
1300
+ * sorts before it can answer a query about one.
1301
+ *
1302
+ * @group Score
1303
+ * @category Useful
1304
+ */
1305
+ type ScoreDeclaration = {
1306
+ /**
1307
+ * The boards, by id. At least one: a `score` key with no boards is a game
1308
+ * that meant to declare one and did not, and reading it as "no boards" would
1309
+ * hand it a scoreboard tab with nothing behind it. The id grammar is the
1310
+ * wire's too, and a test keeps the two in step.
1311
+ *
1312
+ * @minProperties 1
1313
+ * @maxProperties 16
1314
+ * @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be lowercase letters, digits, dots, hyphens and underscores' }
1315
+ */
1316
+ boards: Record<string, ScoreBoard>;
1317
+ [key: string]: unknown;
1318
+ };
1319
+ /**
1320
+ * One named step on the way to a measured achievement's target.
1321
+ *
1322
+ * **A tier is a label, never a second achievement.** The achievement is
1323
+ * unlocked at its `target` and nowhere else; a tier is what a panel calls the
1324
+ * player between here and there — *Silver, 50 of 100*. Modelling each tier as
1325
+ * its own id would be three rows in every board that draws them and three
1326
+ * unlocks a game has to remember to send, where this is one number reported
1327
+ * once and read three ways.
1328
+ *
1329
+ * @group Achievements
1330
+ */
1331
+ type AchievementTier = {
1332
+ /**
1333
+ * The reported value this tier is reached at, on the near side of the target.
1334
+ *
1335
+ * @minimum -9007199254740991
1336
+ * @maximum 9007199254740991
1337
+ */
1338
+ at: number;
1339
+ /**
1340
+ * What to call the player who has reached it.
1341
+ *
1342
+ * @category Important
1343
+ * @pattern \S
1344
+ * @maxLength 40
1345
+ * @errorMessage must not be blank
1346
+ */
1347
+ title: string;
1348
+ /**
1349
+ * A glyph for this tier, where it wants its own.
1350
+ *
1351
+ * @pattern \S
1352
+ * @maxLength 8
1353
+ * @errorMessage must not be blank
1354
+ */
1355
+ icon?: string;
1356
+ [key: string]: unknown;
1357
+ };
1358
+ /**
1359
+ * What makes an achievement measured rather than flagged.
1360
+ *
1361
+ * **`direction` is the one thing that cannot be guessed**, exactly as it cannot
1362
+ * be guessed for a board. `desc` — the default, and what most achievements are
1363
+ * — counts up to the target: *break 100 blocks*. `asc` counts down to it:
1364
+ * *finish under two minutes*, where the value kept is the player's best time
1365
+ * and a smaller one beats a larger. A panel that assumed bigger-is-better would
1366
+ * draw a stopwatch achievement as though every slow run were progress.
1367
+ *
1368
+ * **`format` is how to draw the number**, for the reason a board needs one:
1369
+ * whoever renders this has never run the game and cannot ask it what `112400`
1370
+ * means.
1371
+ *
1372
+ * Both are `ScoreDirection` and `ScoreFormat` rather than types of their own.
1373
+ * "Which way is better" and "how is this number written" are the same two
1374
+ * questions wherever they are asked, and a second spelling of each is two
1375
+ * vocabularies to keep in step and one bug when they drift.
1376
+ *
1377
+ * @group Achievements
1378
+ */
1379
+ type AchievementProgress = {
1380
+ /**
1381
+ * The value that unlocks it. Reaching it is the whole of being unlocked.
1382
+ *
1383
+ * Bounded by `Number.MAX_SAFE_INTEGER`, for the reason `SCORE_VALUE_MAX` is:
1384
+ * past there a whole number stops round-tripping through JSON and through a
1385
+ * 64-bit column, and progress toward a target nobody can reproduce is
1386
+ * progress nobody can draw.
1387
+ *
1388
+ * @minimum -9007199254740991
1389
+ * @maximum 9007199254740991
1390
+ */
1391
+ target: number;
1392
+ /** Which way is better. Absent is `desc`: more is better. */
1393
+ direction?: ScoreDirection;
1394
+ /** How to draw the value. Absent is `integer`. */
1395
+ format?: ScoreFormat;
1396
+ /**
1397
+ * Named milestones on the way, ordered toward the target.
1398
+ *
1399
+ * @maxItems 8
1400
+ */
1401
+ tiers?: AchievementTier[];
1402
+ [key: string]: unknown;
1403
+ };
1404
+ /**
1405
+ * One thing a player can be shown to have done.
1406
+ *
1407
+ * **Two kinds, told apart by one key.** Without `progress` this is a flag: it
1408
+ * happens once, and saying so twice is the same fact twice. With `progress` it
1409
+ * is measured — a count, a distance, a best time — reported as the game goes
1410
+ * and unlocked when the value reaches the target.
1411
+ *
1412
+ * **`secret` withholds the words, never the row.** A secret achievement is
1413
+ * listed while it is locked, so a player can see there is one left, with its
1414
+ * title and detail held back until it is theirs. Hiding the row itself would
1415
+ * make the list's length change under the player, which reads as a bug rather
1416
+ * than as a surprise. It is a courtesy of the panel and not a secret kept from
1417
+ * the machine: the manifest ships beside the bundle and is public, so anybody
1418
+ * who wants the words already has them.
1419
+ *
1420
+ * @group Achievements
1421
+ */
1422
+ type Achievement = {
1423
+ /**
1424
+ * What to call it in front of a player.
1425
+ *
1426
+ * @category Important
1427
+ * @pattern \S
1428
+ * @maxLength 40
1429
+ * @errorMessage must not be blank
1430
+ */
1431
+ title: string;
1432
+ /**
1433
+ * One line under the title, saying what it takes.
1434
+ *
1435
+ * @pattern \S
1436
+ * @maxLength 120
1437
+ * @errorMessage must not be blank
1438
+ */
1439
+ detail?: string;
1440
+ /**
1441
+ * A short glyph to draw beside it — an emoji, usually. Eight characters is
1442
+ * enough for an emoji with a modifier on it.
1443
+ *
1444
+ * @pattern \S
1445
+ * @maxLength 8
1446
+ * @errorMessage must not be blank
1447
+ */
1448
+ icon?: string;
1449
+ /** Whether the words are withheld until it is unlocked. See above. */
1450
+ secret?: boolean;
1451
+ /** Present makes this measured rather than flagged. See `AchievementProgress`. */
1452
+ progress?: AchievementProgress;
1453
+ [key: string]: unknown;
1454
+ };
1455
+ /**
1456
+ * The achievements this game keeps for a player.
1457
+ *
1458
+ * **A map, for the reason `score.boards` is one**: the id travels in
1459
+ * `achievement.unlock`, which is frozen game-side code, so the shape has to
1460
+ * hold a second entry without anything changing meaning.
1461
+ *
1462
+ * **Read by the host, by the client and by the server, and by none of them from
1463
+ * the game.** The site draws this panel for a game sitting in the feed that has
1464
+ * never been run; the server refuses an id no publisher declared. That is what
1465
+ * earns a manifest key rather than a runtime call — the same test `score`
1466
+ * passed.
1467
+ *
1468
+ * @group Achievements
1469
+ * @category Useful
1470
+ */
1471
+ type AchievementDeclaration = {
1472
+ /**
1473
+ * The achievements, by id. At least one: an `achievements` key with nothing
1474
+ * in it is a game that meant to declare one and did not. The id grammar is
1475
+ * the wire's too, and a test keeps the two in step.
1476
+ *
1477
+ * @minProperties 1
1478
+ * @maxProperties 128
1479
+ * @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be lowercase letters, digits, dots, hyphens and underscores' }
1480
+ */
1481
+ list: Record<string, Achievement>;
1482
+ [key: string]: unknown;
1483
+ };
1484
+ /**
1485
+ * A device a game can be played with, as one word.
1486
+ *
1487
+ * Hardware rather than API surface: `mouse` and `touch` are separate words even
1488
+ * though a game reads both through one pointer, because what a *client* does
1489
+ * with this is decide whether to put a game in front of somebody who has no way
1490
+ * to play it.
1491
+ *
1492
+ * @group Input
1493
+ */
1494
+ type InputDevice = 'keyboard' | 'mouse' | 'touch' | 'gamepad';
1495
+ /**
1496
+ * One set of devices that together are enough to play.
1497
+ *
1498
+ * @group Input
1499
+ * @minItems 1
1500
+ * @maxItems 8
1501
+ */
1502
+ type InputDeviceSet = InputDevice[];
1503
+ /**
1504
+ * A name for something that can *press* an action.
1505
+ *
1506
+ * `key.<code>` names a physical key by `KeyboardEvent.code` — **the code and
1507
+ * never the character**. `KeyA` is the key where A sits on a US keyboard and
1508
+ * stays that key on AZERTY, where it types Q, so a game that binds movement to
1509
+ * codes has WASD under the same four fingers everywhere. `Escape` is the host's:
1510
+ * it leaves fullscreen and pointer lock, which are invisible from inside the
1511
+ * frame, and a game that bound it would lose that fight quietly on every press.
1512
+ *
1513
+ * `pad.south` is the south of either cluster: the face button on one pad and the
1514
+ * d-pad's down on another, which is what makes one binding cover both. Compass
1515
+ * words for both clusters, with no `pad.dpad.down` alias beside
1516
+ * `pad.left.south` — a vocabulary that can never shrink should not start out
1517
+ * doubled. A thumbstick is not one of these; see `InputStick`.
1518
+ *
1519
+ * @group Input
1520
+ * @pattern ^(?:key\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|pointer\.(?:primary|secondary)|mouse\.(?:left|middle|right)|touch\.tap|controls\.(?=.{1,48}$)[a-z0-9]+(?:[_-][a-z0-9]+)*|pad\.(?:north|south|east|west|start|select)|pad\.(?:left|right)\.(?:north|south|east|west|bumper|trigger))$
1521
+ * @errorMessage is not a binding this SDK knows (a thumbstick is read rather than bound, and Escape is the host's key)
1522
+ */
1523
+ type InputBinding$1 = string;
1524
+ /**
1525
+ * A name for a thumbstick — something a game reads, never binds.
1526
+ *
1527
+ * @group Input
1528
+ * @pattern ^(?:controls\.(?=.{1,48}$)[a-z0-9]+(?:[_-][a-z0-9]+)*|pad\.stick|pad\.(?:left|right)\.stick)$
1529
+ * @errorMessage must name a thumbstick, such as pad.left.stick
1530
+ */
1531
+ type InputStick = string;
1532
+ /**
1533
+ * What presses an action: at least one binding, and at most 8.
1534
+ *
1535
+ * @group Input
1536
+ * @minItems 1
1537
+ * @maxItems 8
1538
+ */
1539
+ type InputBindings = InputBinding$1[];
1540
+ /**
1541
+ * One thing a player does, named by the game and bound to whatever produces it.
1542
+ *
1543
+ * **An action is a button and never an axis.** A key, a face button on a pad, a
1544
+ * pointer button and an on-screen button all press the same action, because
1545
+ * from the game's side they are the same event. A thumbstick is not one of
1546
+ * those and is read through its own API — folding the two together would put a
1547
+ * range on every binding that mostly does not have one, and would leave a game
1548
+ * unable to say whether it meant the press or the lean.
1549
+ *
1550
+ * **An object rather than a bare list of bindings**, from the first version,
1551
+ * because two strings are wanted the moment anybody draws this: a `title` for
1552
+ * the remapping screen, which shows actions to a player and cannot show
1553
+ * `move.left`, and a `label` for the face of an on-screen button. Growing a
1554
+ * list into an object later would change what an action's value *is*, which is
1555
+ * the one move the additive rule forbids. Only the object is accepted, even for
1556
+ * an action that needs neither string: one way to write an action is one way to
1557
+ * read it.
1558
+ *
1559
+ * @group Input
1560
+ */
1561
+ type InputAction = {
1562
+ /** What presses it. See `InputBinding` for the vocabulary. */
1563
+ bind: InputBindings;
1564
+ /**
1565
+ * What to call it in front of a player, in a remapping screen.
1566
+ *
1567
+ * @pattern \S
1568
+ * @maxLength 40
1569
+ * @errorMessage must not be blank
1570
+ */
1571
+ title?: string;
1572
+ /**
1573
+ * What to draw on an on-screen button: a glyph, or a letter.
1574
+ *
1575
+ * @pattern \S
1576
+ * @maxLength 8
1577
+ * @errorMessage must not be blank
1578
+ */
1579
+ label?: string;
1580
+ [key: string]: unknown;
1581
+ };
1582
+ /**
1583
+ * Where on the screen a group of on-screen controls sits.
1584
+ *
1585
+ * @group Input
1586
+ */
1587
+ type ControlZone = 'left' | 'right' | 'full';
1588
+ /**
1589
+ * How a group of on-screen buttons is arranged.
1590
+ *
1591
+ * Arrangement only, with the count doing the rest, so three across and five
1592
+ * down need no words of their own. **A shape nobody recognises is drawn as a
1593
+ * `column`** — the same escape `Obstruction.reserved` is. Whoever renders these
1594
+ * may be older than the game that declared them, and a control that fails to
1595
+ * appear is a game that cannot be played at all. It is still refused at
1596
+ * publish, where saying so is cheap.
1597
+ *
1598
+ * @group Input
1599
+ */
1600
+ type ControlShape = 'row' | 'column' | 'diamond' | 'cluster';
1601
+ /**
1602
+ * One on-screen control: a group of buttons, or a thumbstick.
1603
+ *
1604
+ * **A button slot names an action; a stick slot names a stick.** The asymmetry
1605
+ * is the hardware's. On-screen buttons have no fixed count or position — three
1606
+ * across, five down, four in a diamond — so there is no standard control to
1607
+ * unify them with, and the action is the only stable name they have. Thumbsticks
1608
+ * come in twos and everybody agrees which is which, so a virtual one says which
1609
+ * it stands for and a game reads the physical stick and this one through one id.
1610
+ *
1611
+ * There is no d-pad here. A d-pad is four buttons wherever it exists — four
1612
+ * entries on the wire from a real pad, four keys on a keyboard — so a layout
1613
+ * that wants one declares four buttons and binds them like anything else.
1614
+ *
1615
+ * @group Input
1616
+ * @if { properties: { type: { const: 'buttons' } }, required: ['type'] }
1617
+ * @then { required: ['buttons'], properties: { as: { type: 'object', additionalProperties: { $ref: '#/definitions/InputBinding' } }, axis: { not: {}, errorMessage: 'is only for a thumbstick' } } }
1618
+ * @else { properties: { as: { $ref: '#/definitions/InputStick' } } }
1619
+ */
1620
+ type ControlSlot = {
1621
+ type: 'buttons' | 'thumbstick';
1622
+ /**
1623
+ * `buttons` only: the actions this control presses, in drawing order.
1624
+ *
1625
+ * @minItems 1
1626
+ * @maxItems 8
1627
+ */
1628
+ buttons?: string[];
1629
+ /** `buttons` only. See `ControlShape`. Absent is `column`. */
1630
+ shape?: ControlShape;
1631
+ /**
1632
+ * What this control stands in for on a standard pad.
1633
+ *
1634
+ * On a thumbstick it is the stick id a game reads — `pad.left.stick` — so one
1635
+ * `onStick` sees the physical stick and this one alike. Left out, the stick is
1636
+ * only itself, which a game reads as `controls.<id>`. On a group of buttons it
1637
+ * is a map from action to standard button, optional and rarely wanted: a game
1638
+ * whose bindings already name `pad.south` needs nothing here, and this is for
1639
+ * one that also wants the low-level path to see the press.
1640
+ */
1641
+ as?: InputStick | Record<string, InputBinding$1>;
1642
+ /** `thumbstick` only. See `ControlAxis`. Absent, the stick moves both ways. */
1643
+ axis?: ControlAxis;
1644
+ [key: string]: unknown;
1645
+ };
1646
+ /**
1647
+ * The one direction an on-screen thumbstick moves in: `x` across, `y` up and
1648
+ * down.
1649
+ *
1650
+ * For a game that steers along one line — a runner sliding left and right, a
1651
+ * paddle — where a stick that also leans up and down is a thumb wandering off
1652
+ * the line it means. The drawn stick becomes a track along that axis and always
1653
+ * reports the other one as 0. **Only the drawn stick:** a real pad's stick still
1654
+ * reports both, so a game reads the axis it wants from either.
1655
+ *
1656
+ * Whoever draws the controls may be older than the game, and draw a stick that
1657
+ * moves both ways. The SDK zeroes the other axis whatever the renderer reports,
1658
+ * so the game hears one axis either way.
1659
+ *
1660
+ * @group Input
1661
+ */
1662
+ type ControlAxis = 'x' | 'y';
1663
+ /**
1664
+ * The controls in one zone of a layout, by id.
1665
+ *
1666
+ * @group Input
1667
+ * @maxProperties 8
1668
+ * @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be a control id of lowercase letters, digits, dots, hyphens and underscores' }
1669
+ */
1670
+ type ControlZoneSlots = Record<string, ControlSlot>;
1671
+ /**
1672
+ * One named arrangement of on-screen controls.
1673
+ *
1674
+ * A game may declare several and switch between them — a menu wants different
1675
+ * controls from play, and a driving section different ones again — which is why
1676
+ * they are named rather than there being one. A zone nobody knows is a layout
1677
+ * from a newer SDK, which the additive rule says must still load, so whoever
1678
+ * draws it simply has nowhere to put it.
1679
+ *
1680
+ * @group Input
1681
+ */
1682
+ type ControlLayout = {
1683
+ left?: ControlZoneSlots;
1684
+ right?: ControlZoneSlots;
1685
+ full?: ControlZoneSlots;
1686
+ [key: string]: unknown;
1687
+ };
1688
+ /**
1689
+ * What a game says about how it is played.
1690
+ *
1691
+ * **It passes the manifest's test twice.** The host must know before the bundle
1692
+ * runs — whether on-screen controls apply at all, and what a remapping screen
1693
+ * offers, are settled before there is a game to ask — and it is read by parties
1694
+ * that never run the game: a feed deciding whether to show a keyboard-only game
1695
+ * to somebody holding a phone, and a remapping screen opened from outside a run.
1696
+ *
1697
+ * Action, control and layout ids follow the grammar the wire does, and a test
1698
+ * keeps the two in step.
1699
+ *
1700
+ * @group Input
1701
+ * @category Useful
1702
+ */
1703
+ type InputDeclaration = {
1704
+ /**
1705
+ * The sets of hardware this game can be played with: any one set, every
1706
+ * member of which must be present.
1707
+ *
1708
+ * A flat list of alternatives rather than a tree of `any` and `all`. Every
1709
+ * real answer is a handful of alternatives, and this one renders straight into
1710
+ * *needs a keyboard and a mouse* without anybody having written a solver.
1711
+ *
1712
+ * **The publisher's claim, taken at its word**, exactly as `orientation` is:
1713
+ * nothing here checks it against the bindings. It cannot be checked. A game is
1714
+ * free to read the platform directly — raw key events, an accelerometer, a
1715
+ * microphone — and that is a stated allowance rather than an oversight, so
1716
+ * what a game *binds* never implies what it can be played with.
1717
+ *
1718
+ * @minItems 1
1719
+ * @maxItems 8
1720
+ */
1721
+ supported?: InputDeviceSet[];
1722
+ /**
1723
+ * The actions, by id.
1724
+ *
1725
+ * @maxProperties 64
1726
+ * @propertyNames { pattern: '^[a-z0-9]+(?:[._-][a-z0-9]+)*$', maxLength: 48, errorMessage: 'must be lowercase letters, digits, dots, hyphens and underscores' }
1727
+ */
1728
+ actions?: Record<string, InputAction>;
1729
+ /**
1730
+ * Named arrangements of on-screen controls. See `ControlLayout`.
1731
+ *
1732
+ * @maxProperties 8
1733
+ * @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
+ */
1735
+ controls?: Record<string, ControlLayout>;
1736
+ [key: string]: unknown;
1737
+ };
1738
+ /**
1739
+ * The index signature is the additive-only rule applied to this file: a game
1740
+ * built against a newer SDK declares keys an older host has never heard of, and
1741
+ * must still run there. It costs a typo in a manifest literal going unnoticed
1742
+ * by the compiler, which the validator catches instead.
1743
+ *
1744
+ * @group Manifest
1745
+ * @category Important
1746
+ */
1747
+ type GameManifest = {
1748
+ /** @category Important */
1749
+ schema: typeof MANIFEST_SCHEMA;
1750
+ /**
1751
+ * Stable across versions, and unique among its publisher's games: it is the
1752
+ * second half of the address the game is published at, `owner/id`, and the
1753
+ * name every new version is filed under. Lowercase, url-safe, and no two
1754
+ * hyphens in a row, because it ends up in urls and storage keys.
1755
+ *
1756
+ * @category Important
1757
+ * @pattern ^[a-z0-9]+(?:-[a-z0-9]+)*$
1758
+ * @maxLength 64
1759
+ * @errorMessage must be lowercase letters, digits and hyphens
1760
+ */
1761
+ id: string;
1762
+ /**
1763
+ * The handle of the account the game is published under: the first half of
1764
+ * `owner/id`. Absent, it is whoever publishes it. Present, only that account
1765
+ * and the game's collaborators may publish it.
1766
+ *
1767
+ * @category Useful
1768
+ * @pattern ^[a-z0-9]+(?:-[a-z0-9]+)*$
1769
+ * @maxLength 64
1770
+ * @errorMessage must be a handle: lowercase letters, digits and hyphens
1771
+ */
1772
+ owner?: string;
1773
+ /**
1774
+ * The game's own version, `MAJOR.MINOR.PATCH`. Ours to read, never to parse for meaning.
1775
+ *
1776
+ * @category Important
1777
+ * @pattern ^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$
1778
+ * @errorMessage must look like 1.0.0
1779
+ */
1780
+ version: string;
1781
+ /**
1782
+ * @category Important
1783
+ * @pattern \S
1784
+ * @maxLength 60
1785
+ * @errorMessage must not be blank
1786
+ */
1787
+ title: string;
1788
+ /**
1789
+ * A sentence or two about the game, shown beside its title.
1790
+ *
1791
+ * @category Useful
1792
+ * @pattern \S
1793
+ * @maxLength 500
1794
+ * @errorMessage must not be blank
1795
+ */
1796
+ description?: string;
1797
+ /**
1798
+ * A picture of the game, shown wherever a link to it is pasted — a chat, a
1799
+ * post, a message.
1800
+ *
1801
+ * The path of a PNG, JPEG or WebP file, relative to the config the manifest
1802
+ * is in, and at most `COVER_MAX_BYTES`. `hopple publish` uploads it beside
1803
+ * the game. 1200 × 630 is the shape every platform shows whole; without one,
1804
+ * a link shows Hopple's own card.
1805
+ *
1806
+ * @category Useful
1807
+ * @pattern ^(?!/)(?!.*\.\.)[A-Za-z0-9._/-]+\.(?:png|jpe?g|webp)$
1808
+ * @maxLength 200
1809
+ * @errorMessage must be the path of a .png, .jpg or .webp file beside the config, like "cover.png"
1810
+ */
1811
+ cover?: string;
1812
+ /** @category Important */
1813
+ 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
+ /**
1831
+ * How the game wants the host's furniture handled. See `InsetMode`.
1832
+ *
1833
+ * @category Useful
1834
+ */
1835
+ layout?: LayoutDeclaration;
1836
+ /**
1837
+ * The custom events this game sends. See `AnalyticsDeclaration`.
1838
+ *
1839
+ * @category Useful
1840
+ */
1841
+ analytics?: AnalyticsDeclaration;
1842
+ /**
1843
+ * That this game keeps state, and roughly how much. See `SaveDeclaration`.
1844
+ *
1845
+ * @category Useful
1846
+ */
1847
+ save?: SaveDeclaration;
1848
+ /**
1849
+ * The boards this game keeps a score on. See `ScoreDeclaration`.
1850
+ *
1851
+ * @category Useful
1852
+ */
1853
+ score?: ScoreDeclaration;
1854
+ /**
1855
+ * What a player can be shown to have done. See `AchievementDeclaration`.
1856
+ *
1857
+ * @category Useful
1858
+ */
1859
+ achievements?: AchievementDeclaration;
1860
+ /**
1861
+ * How this game is played, and what it draws to be played with. See `InputDeclaration`.
1862
+ *
1863
+ * @category Useful
1864
+ */
1865
+ input?: InputDeclaration;
1866
+ /**
1867
+ * What this game makes noise with, one channel at a time, or `null` for a
1868
+ * game that makes none. See `AudioDeclaration`.
1869
+ *
1870
+ * @category Important
1871
+ */
1872
+ audio: AudioDeclaration | null;
1873
+ [key: string]: unknown;
1874
+ };
1875
+ /**
1876
+ * A game project's hopple config: `hopple.config.ts`, `hopple.config.js` or
1877
+ * `hopple.config.json`, beside the project's `package.json`.
1878
+ *
1879
+ * Everything the `hopple` command line needs to know about a project, in one
1880
+ * place: how to build the game, which built file is the game, and the manifest
1881
+ * that says what the game is. Only the manifest leaves the machine, uploaded
1882
+ * beside the page as `game.manifest.json`. A TypeScript or JavaScript config
1883
+ * default-exports one of these; a JSON config is one.
1884
+ *
1885
+ * ```ts
1886
+ * import type { HoppleConfig } from '@hopple/sdk';
1887
+ *
1888
+ * export default {
1889
+ * build: 'npm run build',
1890
+ * page: 'dist/index.html',
1891
+ * manifest: { schema: 1, id: 'my-game', version: '0.1.0', title: 'My Game', orientation: 'portrait', sdk: '^0.1.0', audio: null },
1892
+ * } satisfies HoppleConfig;
1893
+ * ```
1894
+ *
1895
+ * @group Manifest
1896
+ * @category Important
1897
+ */
1898
+ type HoppleConfig = {
1899
+ /** Where an editor finds this file's schema, in a JSON config. Read by nothing else. */
1900
+ $schema?: string;
1901
+ /**
1902
+ * The command that builds the game, run in the config's directory before
1903
+ * `hopple dev`, `check` and `publish` use it, so none of them sees a stale
1904
+ * build. Leave it out for a game with nothing to build.
1905
+ *
1906
+ * @pattern \S
1907
+ * @errorMessage must be the command that builds the game, like "npm run build"
1908
+ */
1909
+ build?: string;
1910
+ /**
1911
+ * The built page, relative to the config: the one file that is published,
1912
+ * so it has to hold the whole game.
1913
+ *
1914
+ * @pattern \.[Hh][Tt][Mm][Ll]?$
1915
+ * @errorMessage must be the built .html file, like "dist/index.html"
1916
+ */
1917
+ page: string;
1918
+ /** What the game is. Uploaded beside the page, and read by every host before the game runs. */
1919
+ manifest: GameManifest;
1920
+ };
1921
+ //#endregion
1922
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/achievements.d.ts
1923
+ /**
1924
+ * Where this player stands on one achievement.
1925
+ *
1926
+ * @group Achievements
1927
+ */
1928
+ type AchievementStatus<Id extends string = string> = {
1929
+ readonly id: Id;
1930
+ /** Whether it is theirs. */
1931
+ readonly unlocked: boolean;
1932
+ /** The host's clock, when it became theirs. Absent while it is not. */
1933
+ readonly at?: number;
1934
+ /**
1935
+ * The best value reported so far, on a measured achievement. Absent for one
1936
+ * nothing has been reported against, which is not the same as zero.
1937
+ */
1938
+ readonly progress?: number;
1939
+ };
1940
+ /**
1941
+ * Every achievement this host takes, in the order the manifest declares them,
1942
+ * and whether the host knows where this player stands on them.
1943
+ *
1944
+ * `known: false` is the host not having an answer — a store still replying, a
1945
+ * client that cannot reach its server — and its items carry an id and nothing
1946
+ * else, so there is no `unlocked` to read until `known` has been checked. A
1947
+ * game that drew "locked" there would be telling somebody they had lost
1948
+ * everything because a network was slow.
1949
+ *
1950
+ * @group Achievements
1951
+ */
1952
+ type AchievementStanding<Id extends string = string> = {
1953
+ readonly known: true;
1954
+ readonly items: readonly AchievementStatus<Id>[];
1955
+ } | {
1956
+ readonly known: false;
1957
+ readonly items: readonly {
1958
+ readonly id: Id;
1959
+ }[];
1960
+ };
1961
+ /** @group Achievements */
1962
+ type AchievementListener<Id extends string = string> = (now: AchievementStanding<Id>, previous: AchievementStanding<Id>) => void;
1963
+ /**
1964
+ * @group Achievements
1965
+ * @category Important
1966
+ */
1967
+ type Achievements<Id extends string = string, Measured extends string = Id> = {
1968
+ /**
1969
+ * Whether this host keeps achievements at all. Known at the handshake: a
1970
+ * game with none leaves out its achievements screen. Reporting to a host
1971
+ * without them does nothing, so a game need not check before it reports.
1972
+ */
1973
+ readonly available: boolean;
1974
+ /**
1975
+ * Where this player stands on every achievement, read without asking.
1976
+ *
1977
+ * The same object until something changes, and frozen, so a game can hold
1978
+ * on to it and compare it with `===`.
1979
+ */
1980
+ readonly standing: AchievementStanding<Id>;
1981
+ /**
1982
+ * Runs when `standing` changes: the host's answer arriving after the welcome,
1983
+ * or a report moving something.
1984
+ *
1985
+ * Not called for the opening standing: that arrived with the welcome, before
1986
+ * there was anything to listen with. Read `standing` once and subscribe.
1987
+ */
1988
+ on(listener: AchievementListener<Id>): Unsubscribe;
1989
+ /**
1990
+ * This achievement is theirs, whatever its progress says. The host announces
1991
+ * it the first time, so a game that calls this on every level-up shows one
1992
+ * banner.
1993
+ *
1994
+ * Legitimate on a measured achievement too, and it finishes it: the progress
1995
+ * goes to the target rather than leaving something unlocked at 40 of 100.
1996
+ *
1997
+ * An id this game's manifest never declared is reported to `onError`, as an
1998
+ * author's mistake and not a player's.
1999
+ */
2000
+ unlock(id: Id): void;
2001
+ /**
2002
+ * How far along a measured achievement this player is, as an absolute value.
2003
+ *
2004
+ * *At 40*, not *add one*. The host keeps whichever value the manifest calls
2005
+ * better — the larger on an ordinary count, the smaller on a best time — so
2006
+ * reporting the same number twice does nothing, and reporting a worse one is
2007
+ * not a regression. Reaching the target unlocks it.
2008
+ *
2009
+ * Against an achievement declared as a flag, the host refuses it and the
2010
+ * refusal is reported to `onError`: there is no target to move toward, and a
2011
+ * host that quietly unlocked it would be hiding the mistake.
2012
+ */
2013
+ progress(id: Measured, value: number): void;
2014
+ };
2015
+ //#endregion
2016
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/analytics.d.ts
2017
+ /**
2018
+ * @group Analytics
2019
+ * @category Important
2020
+ */
2021
+ type Analytics<Events extends object = Record<string, AnalyticsProps>> = {
2022
+ /**
2023
+ * Say that something happened.
2024
+ *
2025
+ * The name must be one the game declared in its manifest, or the host will
2026
+ * refuse it. A name or a prop that cannot travel goes to `onError` — the game
2027
+ * author's own channel, so a mistake is loud where it is cheap and harmless
2028
+ * where it is not — and a refused prop costs its column rather than the event.
2029
+ */
2030
+ track<Event extends keyof Events & string>(name: Event, props?: Events[Event]): void;
2031
+ };
2032
+ //#endregion
2033
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/audio.d.ts
2034
+ /**
2035
+ * Something about the game's sound changed: the host's order, the player's
2036
+ * mute, a fader. Read what you need — `volume(channel)`, `allowed` — and apply
2037
+ * it at once.
2038
+ *
2039
+ * @group Audio
2040
+ */
2041
+ type AudioListener = () => void;
2042
+ /**
2043
+ * @group Audio
2044
+ * @category Important
2045
+ */
2046
+ type Audio<Channel extends string = string> = {
2047
+ /**
2048
+ * Whether the host lets the game make sound now. Known as soon as `init`
2049
+ * resolves.
2050
+ *
2051
+ * `volume()` is already 0 while this is `false`, so a game that mixes from
2052
+ * `volume()` obeys it without reading it. Read it to pause an engine's own
2053
+ * sound rather than leave it playing silently.
2054
+ */
2055
+ readonly allowed: boolean;
2056
+ /**
2057
+ * Everything off, whatever the faders say.
2058
+ *
2059
+ * Not the same fact as `master === 0`, and a game must not fold the two
2060
+ * together: a player who mutes and unmutes expects the mix they had back.
2061
+ */
2062
+ readonly muted: boolean;
2063
+ /** The fader over the whole game, `0`–`1`, ignoring mute. */
2064
+ readonly master: number;
2065
+ /** The whole of what the host last said. Frozen; a new object on every change. */
2066
+ readonly state: AudioState;
2067
+ /**
2068
+ * How loud one of this game's channels should actually come out, `0`–`1`.
2069
+ *
2070
+ * **The number to multiply a sound by, and the only one worth reading.** It
2071
+ * is the host's order, mute, the master fader and the channel's own, already
2072
+ * folded — so a game
2073
+ * that writes `gain.value = sdk.audio.volume('music')` is correct, and one
2074
+ * that reads the three separately is one refactor away from forgetting mute.
2075
+ *
2076
+ * A channel this game never declared reads as its master, which is what a
2077
+ * game asking about a channel its manifest does not carry deserves: audible,
2078
+ * and governed by the one fader that certainly exists. Called with nothing,
2079
+ * or with `master`, it is the master fader itself.
2080
+ */
2081
+ volume(channel?: Channel): number;
2082
+ /**
2083
+ * The channel's own fader, unmixed and ignoring mute.
2084
+ *
2085
+ * For a game drawing its own volume screen. What a sound is multiplied by is
2086
+ * `volume()`, and mixing with this one instead is how a mute button stops
2087
+ * working.
2088
+ */
2089
+ channel(id: Channel): number;
2090
+ /**
2091
+ * Told whenever any of it changes: the host's order, and the moment the
2092
+ * player mutes or moves a fader.
2093
+ *
2094
+ * **Apply it at once.** The host is answered as soon as every listener has
2095
+ * been called, and nothing returned is waited for — a mute that fades out
2096
+ * over half a second is half a second of sound the player asked not to hear.
2097
+ * A ramp of a few milliseconds, to keep a gain jump from clicking, is fine;
2098
+ * it just is not something to wait on.
2099
+ *
2100
+ * Not called for the opening state: that arrived with the welcome, before
2101
+ * there was anything to listen with. Apply it once and subscribe.
2102
+ */
2103
+ on(listener: AudioListener): Unsubscribe;
2104
+ /**
2105
+ * A gain node for one channel of a Web Audio context, kept at that channel's
2106
+ * volume: connect every sound on the channel to it instead of to
2107
+ * `context.destination`.
2108
+ *
2109
+ * The context is looked after too: suspended whenever sound is not
2110
+ * `allowed` (and the host told only once it has gone quiet), resumed when it
2111
+ * is, and resumed by the player's first press in the frame where the browser
2112
+ * started it suspended. The same context and channel always give the same
2113
+ * node.
2114
+ */
2115
+ output(context: AudioContext, channel?: Channel): GainNode;
2116
+ /**
2117
+ * Keep an `<audio>` or `<video>` element at one channel's volume, and paused
2118
+ * whenever sound is not `allowed` — playing again when it is, if it was
2119
+ * playing when it stopped.
2120
+ *
2121
+ * Returns a function that lets the element go again.
2122
+ */
2123
+ media(element: HTMLMediaElement, channel?: Channel): Unsubscribe;
2124
+ };
2125
+ //#endregion
2126
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/info.d.ts
2127
+ /**
2128
+ * What the host said, frozen.
2129
+ *
2130
+ * Frozen because it is a record of what a game was told, and a game that could
2131
+ * edit it would be writing a bug that only surfaces somewhere else entirely,
2132
+ * when something reads the field back and believes it.
2133
+ *
2134
+ * @group Info
2135
+ * @category Important
2136
+ */
2137
+ type Info = Readonly<{
2138
+ app: Readonly<AppInfo>;
2139
+ game: Readonly<GameInfo>;
2140
+ player: Readonly<PlayerInfo>;
2141
+ device: Readonly<DeviceInfo>;
2142
+ }>;
2143
+ //#endregion
2144
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/layout.d.ts
2145
+ /**
2146
+ * How much of each edge belongs to the host. CSS pixels.
2147
+ *
2148
+ * @group Layout
2149
+ * @category Useful
2150
+ */
2151
+ type Insets = {
2152
+ top: number;
2153
+ right: number;
2154
+ bottom: number;
2155
+ left: number;
2156
+ };
2157
+ /**
2158
+ * What is over the game. Facts only — no projection, because which projection
2159
+ * is the right one is the game's to decide.
2160
+ *
2161
+ * @group Layout
2162
+ */
2163
+ type LayoutState = {
2164
+ /**
2165
+ * The frame the rectangles are measured against, as the host last set it:
2166
+ * the page's own window, so `innerWidth` and `innerHeight` agree with it from
2167
+ * the moment `init` resolves.
2168
+ */
2169
+ readonly frame: Size;
2170
+ /**
2171
+ * Everything over the frame, whoever put it there: the host's rail and the
2172
+ * device's notch, and the SDK's own on-screen controls where a game is
2173
+ * showing some. One list, because a game avoids all of them the same way.
2174
+ */
2175
+ readonly obstructions: readonly Obstruction[];
2176
+ /**
2177
+ * The default projection of everything reserved onto the four edges, for the
2178
+ * majority of games that want exactly that.
2179
+ *
2180
+ * It is a convenience, not the truth: a game that wants to ignore cutouts, or
2181
+ * to keep clear of overlays as well, calls `insetsFrom` over whatever subset
2182
+ * of `obstructions` it means. A game that scrolls wants a second reading
2183
+ * beside this one — see `InsetPolicy`.
2184
+ */
2185
+ readonly insets: Insets;
2186
+ };
2187
+ /** @group Layout */
2188
+ type LayoutListener = (now: LayoutState, previous: LayoutState) => void;
2189
+ /**
2190
+ * @group Layout
2191
+ * @category Important
2192
+ */
2193
+ type Layout = LayoutState & {
2194
+ /** Runs whenever the frame or the rectangles change. */
2195
+ on(listener: LayoutListener): Unsubscribe;
2196
+ };
2197
+ /**
2198
+ * Which rectangles an inset is being asked about.
2199
+ *
2200
+ * `all` is the ordinary reading and the default. `strips` is for the one thing
2201
+ * insets cannot express: see `only` below.
2202
+ *
2203
+ * @group Layout
2204
+ * @category Useful
2205
+ */
2206
+ type InsetPolicy = {
2207
+ /**
2208
+ * `all` — every reserved rectangle. `strips` — only those that run the whole
2209
+ * length of an edge, which on a real host means the rail and nothing else.
2210
+ *
2211
+ * The distinction exists because of scrollbars, and it is worth spelling out
2212
+ * since nothing else in this file has to care. A scrollbar is painted on the
2213
+ * border edge of whatever scrolls, and padding lives inside that edge — so a
2214
+ * game that scrolls cannot move its scrollbar off the host's rail by paying
2215
+ * `insets`, however honestly it pays them. The only thing that moves it is
2216
+ * making the scrolling element itself stop short, which means insets that say
2217
+ * *the host has taken this whole edge* rather than *the host is somewhere over
2218
+ * here*.
2219
+ *
2220
+ * A corner control is the case the two answers differ on, and `strips` drops
2221
+ * it deliberately: it costs the least area on a side edge, so `all` turns a
2222
+ * 56-pixel square into a 56-pixel column down the entire height — most of a
2223
+ * sidebar's worth of scrolling content for one button. What a game loses by
2224
+ * dropping it is the far end of its scrollbar, under a corner the host's
2225
+ * control was always going to sit on.
2226
+ *
2227
+ * Content is the other half and is not this function's problem: keeping text
2228
+ * and buttons off a corner control is what `all` and a padding are for, and
2229
+ * a game doing both reads this twice. The demo does.
2230
+ */
2231
+ only?: 'all' | 'strips';
2232
+ };
2233
+ /**
2234
+ * The cheapest set of edge insets that covers every reserved rectangle.
2235
+ *
2236
+ * Each rectangle is pushed onto one of the four edges — the one where it costs
2237
+ * the least area, which for a corner is the shorter way out. That is what makes
2238
+ * the same corner control a top inset on a phone and a right inset on a desktop
2239
+ * window without anybody deciding it twice.
2240
+ *
2241
+ * A rectangle touching no edge at all still has to be covered by something,
2242
+ * since an inset is all this can say; it gets pushed to its nearest edge and
2243
+ * takes the gap with it. Hosts are expected not to do that, and a game that
2244
+ * wants the truth about one should read the rectangles.
2245
+ *
2246
+ * Exported because it is the whole policy in one pure function: a game that
2247
+ * wants to project differently, or against a size of its own choosing, can.
2248
+ *
2249
+ * @group Layout
2250
+ * @category Useful
2251
+ */
2252
+ declare function insetsFrom(obstructions: readonly Obstruction[], frame: Size, policy?: InsetPolicy): Insets;
2253
+ //#endregion
2254
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/names.d.ts
2255
+ /**
2256
+ * The keys of a declared map, or `string` where the map is not a literal one —
2257
+ * which is what a manifest typed as the wide `GameManifest` gives — and
2258
+ * `never` where nothing is declared.
2259
+ *
2260
+ * @group Names
2261
+ */
2262
+ type DeclaredNames<T> = T extends object ? string extends keyof T ? string : Extract<keyof T, string> : never;
2263
+ /**
2264
+ * An action the manifest declares under `input.actions`.
2265
+ *
2266
+ * @group Names
2267
+ */
2268
+ type ActionName<M> = M extends {
2269
+ input?: {
2270
+ actions?: infer A;
2271
+ };
2272
+ } ? DeclaredNames<NonNullable<A>> : never;
2273
+ /**
2274
+ * An on-screen control layout the manifest declares under `input.controls`.
2275
+ *
2276
+ * @group Names
2277
+ */
2278
+ type LayoutName<M> = M extends {
2279
+ input?: {
2280
+ controls?: infer C;
2281
+ };
2282
+ } ? DeclaredNames<NonNullable<C>> : never;
2283
+ /**
2284
+ * A score board the manifest declares under `score.boards`.
2285
+ *
2286
+ * @group Names
2287
+ */
2288
+ type BoardName<M> = M extends {
2289
+ score?: {
2290
+ boards?: infer B;
2291
+ };
2292
+ } ? DeclaredNames<NonNullable<B>> : never;
2293
+ /**
2294
+ * An achievement the manifest declares under `achievements.list`.
2295
+ *
2296
+ * @group Names
2297
+ */
2298
+ type AchievementName<M> = M extends {
2299
+ achievements?: {
2300
+ list?: infer L;
2301
+ };
2302
+ } ? DeclaredNames<NonNullable<L>> : never;
2303
+ /**
2304
+ * An achievement declared with `progress`: the only kind `progress()` takes.
2305
+ *
2306
+ * @group Names
2307
+ */
2308
+ type MeasuredName<M> = M extends {
2309
+ achievements?: {
2310
+ list?: infer L;
2311
+ };
2312
+ } ? string extends keyof NonNullable<L> ? string : { [K in keyof NonNullable<L>]: NonNullable<L>[K] extends {
2313
+ progress: object;
2314
+ } ? K : never; }[keyof NonNullable<L>] & string : never;
2315
+ /**
2316
+ * An audio channel the manifest declares under `audio.channels`.
2317
+ *
2318
+ * @group Names
2319
+ */
2320
+ type ChannelName<M> = M extends {
2321
+ audio?: {
2322
+ channels?: infer C;
2323
+ } | null;
2324
+ } ? DeclaredNames<NonNullable<C>> : never;
2325
+ /**
2326
+ * An analytics event the manifest declares under `analytics.events`.
2327
+ *
2328
+ * @group Names
2329
+ */
2330
+ type EventName<M> = M extends {
2331
+ analytics?: {
2332
+ events?: infer E;
2333
+ };
2334
+ } ? DeclaredNames<NonNullable<E>> : never;
2335
+ /**
2336
+ * What a property declared as `string`, `number` or `boolean` holds.
2337
+ *
2338
+ * @group Names
2339
+ */
2340
+ type DeclaredValue<T> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : AnalyticsValue;
2341
+ /**
2342
+ * Every declared event with the properties it was declared with, each
2343
+ * optional and typed as declared.
2344
+ *
2345
+ * @group Names
2346
+ */
2347
+ type EventProps<M> = M extends {
2348
+ analytics?: {
2349
+ events?: infer E;
2350
+ };
2351
+ } ? string extends keyof NonNullable<E> ? Record<string, AnalyticsProps> : { [K in keyof NonNullable<E> & string]: { [P in keyof NonNullable<E>[K]]?: DeclaredValue<NonNullable<E>[K][P]>; }; } : Record<never, never>;
2352
+ /**
2353
+ * One of the sticks a game reads: a gamepad's, or an on-screen thumbstick
2354
+ * acting as one, or an on-screen thumbstick of its own (`controls.<name>`).
2355
+ * `pad.stick` is whichever of a pad's two the player is using.
2356
+ *
2357
+ * @group Names
2358
+ */
2359
+ type StickName = 'pad.left.stick' | 'pad.right.stick' | 'pad.stick' | `controls.${string}`;
2360
+ //#endregion
2361
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/input/state.d.ts
2362
+ /**
2363
+ * Where the pointer is, in the frame's own coordinates.
2364
+ *
2365
+ * @group Input
2366
+ */
2367
+ type PointerState = {
2368
+ x: number;
2369
+ y: number;
2370
+ /** Whether its primary button — or the finger — is down. */
2371
+ down: boolean;
2372
+ };
2373
+ /**
2374
+ * How far a stick is pushed, each axis in −1..1.
2375
+ *
2376
+ * @group Input
2377
+ */
2378
+ type StickState = {
2379
+ x: number;
2380
+ y: number;
2381
+ };
2382
+ /**
2383
+ * @group Input
2384
+ */
2385
+ type ActionListener<Action extends string = string> = (down: boolean, action: Action) => void;
2386
+ /**
2387
+ * @group Input
2388
+ */
2389
+ type StickListener = (value: StickState, stick: string) => void;
2390
+ /**
2391
+ * @group Input
2392
+ */
2393
+ type PointerListener = (pointer: PointerState | null) => void;
2394
+ /**
2395
+ * What was true at one moment, and what has happened since the last one.
2396
+ *
2397
+ * Frozen and detached: reading a snapshot twice in a frame gives the same
2398
+ * answers, and holding one past the frame it was taken in tells the truth about
2399
+ * a moment that has passed rather than lying about the present.
2400
+ *
2401
+ * @group Input
2402
+ */
2403
+ type InputSnapshot<Action extends string = string> = {
2404
+ /** Whether the action is down right now. */
2405
+ pressed(action: Action): boolean;
2406
+ /**
2407
+ * Whether it went down since the last read.
2408
+ *
2409
+ * True even when it has already come back up — a tap shorter than one frame
2410
+ * is a real press that pure level polling would miss entirely, and the
2411
+ * commonest thing a player does on a touchscreen.
2412
+ */
2413
+ started(action: Action): boolean;
2414
+ /** Whether it came up since the last read. */
2415
+ ended(action: Action): boolean;
2416
+ /** How far a stick is pushed. `pad.stick` reads whichever one is off centre. */
2417
+ stick(stick: StickName): StickState;
2418
+ /** Where the pointer is, or `null` for a pointer that is nowhere. */
2419
+ pointer: PointerState | null;
2420
+ };
2421
+ //#endregion
2422
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/input/controls.d.ts
2423
+ /**
2424
+ * The name a host leaves its input shim under.
2425
+ *
2426
+ * Beside `SHIM` in spirit and separate from it in fact: that one is a channel a
2427
+ * platform must provide for a game to speak at all, and this one is an opinion
2428
+ * about drawing that most hosts will not have. A game with no shim draws the
2429
+ * default controls and is completely playable — which is what an embed on
2430
+ * somebody else's page gets, since there is nowhere for anyone to inject one.
2431
+ *
2432
+ * @group Input
2433
+ */
2434
+ declare const INPUT_SHIM = "__HOPPLE_INPUT__";
2435
+ /**
2436
+ * One control, as a renderer is given it. Resolved: no ids left to look up.
2437
+ *
2438
+ * @group Input
2439
+ */
2440
+ type ControlModelSlot = {
2441
+ id: string;
2442
+ type: 'buttons' | 'thumbstick';
2443
+ /** Always a shape this SDK knows. An unfamiliar one has already become `column`. */
2444
+ shape: InputLayoutShape;
2445
+ /**
2446
+ * For `buttons`: what each one presses, what to draw on it, and the standard
2447
+ * control it stands in for where the layout said one.
2448
+ */
2449
+ buttons: readonly {
2450
+ action: string;
2451
+ label: string;
2452
+ as?: string;
2453
+ }[];
2454
+ /** For `thumbstick`: the stick id it drives. */
2455
+ stick: string;
2456
+ /**
2457
+ * For `thumbstick`: the one axis it moves along, where the layout held it to
2458
+ * one. Absent, it moves both ways. A renderer that draws it freely anyway is
2459
+ * still correct: the SDK zeroes the other axis of whatever is reported.
2460
+ */
2461
+ axis?: InputLayoutAxis;
2462
+ };
2463
+ /**
2464
+ * @group Input
2465
+ */
2466
+ type ControlModelZone = {
2467
+ zone: InputLayoutZone;
2468
+ controls: readonly ControlModelSlot[];
2469
+ };
2470
+ /**
2471
+ * A whole layout, resolved against the manifest's actions.
2472
+ *
2473
+ * @group Input
2474
+ */
2475
+ type ControlModel = {
2476
+ /** Which named layout this is. */
2477
+ layout: string;
2478
+ zones: readonly ControlModelZone[];
2479
+ /** The frame the controls are being drawn on, when it is known. */
2480
+ frame: Size;
2481
+ /**
2482
+ * How much of that frame the host has taken along each edge.
2483
+ *
2484
+ * The host's furniture only — its rail, its corner control, the device's
2485
+ * notch. Emphatically *not* the pad's own rectangles, which reach a game as
2486
+ * obstructions of kind `controls` and would, if they were projected in here,
2487
+ * inset the pad by the pad on every pass until it walked off the frame.
2488
+ * Whoever fills this in owes that filter; `index.ts` pays it.
2489
+ *
2490
+ * Zero is the ordinary answer and always a safe one: a host that reserved the
2491
+ * frame instead of drawing over it has already shrunk `frame` clear of its
2492
+ * own furniture, and a host too old to say anything has said nothing. Both
2493
+ * land here as four zeroes and the pad sits where it always sat.
2494
+ */
2495
+ insets: Insets;
2496
+ /**
2497
+ * How a thumbstick meets a thumb, as the player set it on their host.
2498
+ *
2499
+ * `floating`: marked where the layout puts it, so a player can see there is
2500
+ * a stick, brought to wherever a thumb lands on its side of the screen, so
2501
+ * the place they first touch is the centre, and pulled along by a thumb that
2502
+ * goes past its rim. `fixed`: moved only from where it is drawn. A renderer
2503
+ * that ignores this draws `fixed`, which is still a stick a game can be
2504
+ * played with.
2505
+ */
2506
+ sticks: InputSticks;
2507
+ };
2508
+ /**
2509
+ * How a renderer says something happened.
2510
+ *
2511
+ * The only way out of a renderer, replaced or not. A shim reports and the SDK
2512
+ * decides: that is what keeps a replaced renderer from being able to leave an
2513
+ * action held forever.
2514
+ *
2515
+ * @group Input
2516
+ */
2517
+ type ControlReport = {
2518
+ /** `index` is into the control's own `buttons`. */
2519
+ press(control: string, index: number, down: boolean): void;
2520
+ /** A stick's position, each axis in −1..1 and already inside the unit circle. */
2521
+ move(control: string, x: number, y: number): void;
2522
+ };
2523
+ /**
2524
+ * What a host may install to change how controls are drawn.
2525
+ *
2526
+ * **Every hook is optional and every one is wrapped.** A shim that throws is
2527
+ * dropped back to the default for that hook and reported to `onError`, because
2528
+ * a replaceable renderer is otherwise a way for one host to ship a broken pad to
2529
+ * every game it runs at once.
2530
+ *
2531
+ * **The SDK declares the version and the shim adapts.** This file is frozen
2532
+ * inside bundles that shipped months before any given shim was written, so the
2533
+ * dangerous direction is a new shim meeting an old SDK — and it is safe by
2534
+ * construction if the shim is the side doing the adapting. A shim reads
2535
+ * `request.contract` and answers with what that version can carry.
2536
+ *
2537
+ * @group Input
2538
+ */
2539
+ type InputShim = {
2540
+ /** The contract version this shim was written against. `1` today. */
2541
+ v: 1;
2542
+ /**
2543
+ * Whether to draw controls for this layout at all.
2544
+ *
2545
+ * The host's half of the answer; the game's half is having asked. Returning
2546
+ * nothing leaves the decision alone, which is what a shim that only wanted to
2547
+ * restyle should do.
2548
+ */
2549
+ decide?(request: {
2550
+ layout: string;
2551
+ frame: Size;
2552
+ /** What the host has taken along each edge — see `ControlModel.insets`. */
2553
+ insets: Insets;
2554
+ contract: number;
2555
+ }): boolean | undefined;
2556
+ /**
2557
+ * How far a stick must move before it counts, 0 to 1.
2558
+ *
2559
+ * The host's rather than the game's, because it is an accessibility setting
2560
+ * before it is a taste: a player whose hands shake needs a wider dead region
2561
+ * in every game they open, not in the ones that happened to think of it.
2562
+ */
2563
+ deadzone?: number;
2564
+ /** A tick when a control is pressed. Absent where the platform has none. */
2565
+ haptic?(kind: 'press' | 'release'): void;
2566
+ /** Called with the root once, to set custom properties or add a stylesheet. */
2567
+ style?(root: ShadowRoot, request: {
2568
+ layout: string;
2569
+ frame: Size;
2570
+ }): void;
2571
+ /**
2572
+ * Draw the controls instead of the SDK.
2573
+ *
2574
+ * Given the root, the resolved model and the one way back. Returns a teardown,
2575
+ * which is called before the next render and when the controls are hidden.
2576
+ */
2577
+ render?(root: ShadowRoot, model: ControlModel, report: ControlReport): (() => void) | void;
2578
+ };
2579
+ /**
2580
+ * @group Input
2581
+ */
2582
+ declare function isInputShim(value: unknown): value is InputShim;
2583
+ /**
2584
+ * The contract version this SDK speaks. Told to a shim so it can answer in kind.
2585
+ *
2586
+ * `2` added `insets` — to the model a renderer is given, to what `decide` is
2587
+ * asked, and as four custom properties the default sheet spends. Purely
2588
+ * additive: a `v: 1` shim that reads none of them behaves exactly as it did.
2589
+ *
2590
+ * `3` added `axis` to a thumbstick in the model. Additive again: a shim that
2591
+ * draws the stick moving both ways still gives the game one axis, because the
2592
+ * other is dropped from what it reports.
2593
+ *
2594
+ * `4` added `sticks` to the model: whether the player wants a thumbstick that
2595
+ * comes to their thumb. Additive: a shim that reads nothing of it draws the
2596
+ * stick where it always did.
2597
+ *
2598
+ * @group Input
2599
+ */
2600
+ declare const INPUT_CONTRACT = 4;
2601
+ //#endregion
2602
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/input/bindings.d.ts
2603
+ /**
2604
+ * The standard mapping's buttons, by index, as the names this SDK uses.
2605
+ *
2606
+ * `Gamepad.mapping === 'standard'` is what makes these indices mean anything;
2607
+ * a pad that reports anything else is read for its axes and ignored for its
2608
+ * buttons, because an unmapped index is a number with no meaning attached.
2609
+ *
2610
+ * **The left cluster is the d-pad and the right is the faces**, which is what
2611
+ * makes `pad.south` cover both *A* and *down* with one word. Compass positions
2612
+ * rather than letters throughout: A and B are swapped between the two
2613
+ * best-selling controllers, so a name built on them is wrong for half the
2614
+ * players who read it.
2615
+ *
2616
+ * @group Input
2617
+ */
2618
+ declare const PAD_BUTTONS: readonly (string | null)[];
2619
+ /**
2620
+ * The binding a player would actually use, given what they are holding.
2621
+ *
2622
+ * A game that draws one prompt for an action with four bindings has to be told
2623
+ * which one, and the answer is the one their hands are on. Falls back to the
2624
+ * first declared binding, which is the game author's own idea of the primary
2625
+ * one.
2626
+ *
2627
+ * @group Input
2628
+ * @category Useful
2629
+ */
2630
+ declare function preferredBinding(bind: readonly string[], scheme: InputScheme): string | undefined;
2631
+ /**
2632
+ * Whether a binding is one the player could press with what they are using.
2633
+ *
2634
+ * @group Input
2635
+ * @category Useful
2636
+ */
2637
+ declare function servesScheme(binding: string, scheme: InputScheme): boolean;
2638
+ /**
2639
+ * What to write on the screen for one binding.
2640
+ *
2641
+ * **Best effort, and the host is allowed to do better.** Whether the south face
2642
+ * button says *A* or *B* depends on whose controller it is, and
2643
+ * `Gamepad.id` is free text that varies by browser and by operating system — so
2644
+ * a host that genuinely knows, because it is a native app, sends its own label
2645
+ * and this is what fills in when nobody did. Getting it slightly wrong is a
2646
+ * prompt a player can still follow; refusing to draw one is a game that never
2647
+ * says which button to press.
2648
+ *
2649
+ * @group Input
2650
+ * @category Useful
2651
+ */
2652
+ declare function labelFor(binding: string, scheme: InputScheme): string;
2653
+ //#endregion
2654
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/input/index.d.ts
2655
+ /**
2656
+ * How far a stick moves before it counts, where no shim has an opinion.
2657
+ *
2658
+ * @group Input
2659
+ * @category Useful
2660
+ */
2661
+ declare const DEADZONE = 0.15;
2662
+ /**
2663
+ * What presses each action, and what the player is pressing with: the part of
2664
+ * input that is state rather than a stream of presses.
2665
+ *
2666
+ * @group Input
2667
+ */
2668
+ type InputState<Action extends string = string> = {
2669
+ /**
2670
+ * What presses each action, and what to call it in front of a player.
2671
+ *
2672
+ * `mapping.shoot.label` is the whole reason this is readable: it is *Z* on a
2673
+ * keyboard, a glyph on whatever pad the player picked up, and the label the
2674
+ * manifest gave the on-screen button on a touch screen — the name written on
2675
+ * whatever the player would press. So a game writes one prompt and is right
2676
+ * on all of them. An action can be missing, so keep a fallback.
2677
+ */
2678
+ readonly mapping: Readonly<Partial<Record<Action, InputBinding>>>;
2679
+ /** What the player is using right now. */
2680
+ readonly scheme: InputScheme;
2681
+ };
2682
+ /**
2683
+ * A player rebound something, or picked up another device. `previous` says
2684
+ * which: a different `scheme` is a new device, a different `mapping` under the
2685
+ * same one is a rebinding.
2686
+ *
2687
+ * @group Input
2688
+ */
2689
+ type InputListener<Action extends string = string> = (now: InputState<Action>, previous: InputState<Action>) => void;
2690
+ /**
2691
+ * What the player is doing, as the frame sees it: `sdk.input`.
2692
+ *
2693
+ * @group Input
2694
+ * @category Important
2695
+ */
2696
+ type Input<Action extends string = string, Layout extends string = string> = InputState<Action> & {
2697
+ /**
2698
+ * Run this whenever an action goes down or comes up.
2699
+ *
2700
+ * One press however many things are bound to it: an action held by a key and
2701
+ * a pad button at once goes down with the first and up with the last, rather
2702
+ * than flickering as a player moves between them.
2703
+ *
2704
+ * An action the manifest never declared is reported to `onError`: it would
2705
+ * never fire.
2706
+ */
2707
+ onAction(action: Action, listener: ActionListener<Action>): Unsubscribe;
2708
+ /** Run this when a stick moves. `pad.stick` hears from either of them. */
2709
+ onStick(stick: StickName, listener: StickListener): Unsubscribe;
2710
+ /** Run this when the pointer moves or its button changes. `null` is *nowhere*. */
2711
+ onPointer(listener: PointerListener): Unsubscribe;
2712
+ /**
2713
+ * Run this when `mapping` or `scheme` changes: a player rebinds something, or
2714
+ * picks up a controller. Frozen and replaced rather than mutated, so a value
2715
+ * read in a frame loop cannot change under it.
2716
+ */
2717
+ on(listener: InputListener<Action>): Unsubscribe;
2718
+ readonly controls: Controls<Layout>;
2719
+ /**
2720
+ * Whether to keep enough state for `read()` to answer about movement.
2721
+ *
2722
+ * Presses are always tracked, so a key already held when this is switched on
2723
+ * is reported honestly by the first read, and a pad's buttons and sticks are
2724
+ * tracked from the moment the player presses something on it. What this gates
2725
+ * is the pointer's position, which costs a listener on every move.
2726
+ */
2727
+ poll: boolean;
2728
+ /**
2729
+ * What is true now, and what has happened since the last read.
2730
+ *
2731
+ * Call it once at the top of a frame: taking a snapshot consumes the edges, so
2732
+ * a second reader in the same frame sees none of the presses the first did.
2733
+ * The pointer's position needs `poll` set; presses and sticks do not.
2734
+ */
2735
+ read(): InputSnapshot<Action>;
2736
+ /**
2737
+ * Stop reading input and let go of everything held.
2738
+ *
2739
+ * For a text field a game drew itself, where the action layer would otherwise
2740
+ * eat the keystrokes. Ordinary inputs and `contenteditable` are stepped around
2741
+ * without being asked; this is for the one case that cannot be seen from
2742
+ * outside — a field drawn on a canvas.
2743
+ */
2744
+ suspend(): void;
2745
+ resume(): void;
2746
+ readonly suspended: boolean;
2747
+ };
2748
+ /**
2749
+ * The on-screen controls: shown when the game says so *and* the host says they
2750
+ * apply.
2751
+ *
2752
+ * Two halves, and a game needs both answers. Asking is the game's — a pad drawn
2753
+ * over a title screen is worse than one that arrives a moment late — and whether
2754
+ * there is any point is the host's, because a desktop with a keyboard has none.
2755
+ *
2756
+ * @group Input
2757
+ * @category Useful
2758
+ */
2759
+ type Controls<Layout extends string = string> = {
2760
+ /**
2761
+ * Draw a layout, if this host will have one.
2762
+ *
2763
+ * The name may be left out by a game that declared exactly one layout. Showing
2764
+ * a different layout while a button is held releases that button, because a
2765
+ * control that has gone away cannot be the thing that ends its own press. A
2766
+ * layout the manifest never declared is reported to `onError`.
2767
+ */
2768
+ show(layout?: Layout): void;
2769
+ /**
2770
+ * Take them down, releasing anything they were holding.
2771
+ *
2772
+ * Hiding is about drawing and nothing else: the keys and pad buttons bound to
2773
+ * the same actions go on working.
2774
+ */
2775
+ hide(): void;
2776
+ /**
2777
+ * Whether controls are on the screen right now.
2778
+ *
2779
+ * The effective answer, not the request: the game asked, *and* the host allows
2780
+ * it, *and* the layout fitted on this frame. A game draws its own touch hints
2781
+ * off this rather than off having called `show`.
2782
+ */
2783
+ readonly showing: boolean;
2784
+ /** The layout the game last asked for, or `null`. */
2785
+ readonly asked: Layout | null;
2786
+ /** Whether this host has any use for on-screen controls at all. */
2787
+ readonly available: boolean;
2788
+ };
2789
+ //#endregion
2790
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/lifecycle.d.ts
2791
+ /** @group Lifecycle */
2792
+ type LifecycleListener = (phase: Phase, previous: Phase) => void | Promise<void>;
2793
+ /**
2794
+ * @group Lifecycle
2795
+ * @category Important
2796
+ */
2797
+ type Lifecycle = {
2798
+ /** How far this game has got. `loading` until it says otherwise. */
2799
+ readonly phase: Phase;
2800
+ /**
2801
+ * Tell the host the game can be shown. Nothing to wait for: `phase` becomes
2802
+ * `ready` once the host has heard, and a failure is reported to `onError`.
2803
+ */
2804
+ ready(): void;
2805
+ /** Runs on every change of phase. Return a promise to hold the reply. */
2806
+ on(listener: LifecycleListener): Unsubscribe;
2807
+ };
2808
+ //#endregion
2809
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/loop.d.ts
2810
+ /**
2811
+ * The host has let the game run its loop, or stopped it. Return a promise to
2812
+ * make the host wait.
2813
+ *
2814
+ * @group Loop
2815
+ */
2816
+ type LoopListener = (allowed: boolean) => void | Promise<void>;
2817
+ /**
2818
+ * One frame of the game's loop: `dt` is the seconds since the last frame,
2819
+ * capped at `MAX_STEP` so a hitch never becomes one enormous step, and 0 on the
2820
+ * first frame after the loop starts or comes back. `now` is the frame's own
2821
+ * timestamp, in milliseconds.
2822
+ *
2823
+ * @group Loop
2824
+ */
2825
+ type FrameCallback = (dt: number, now: number) => void;
2826
+ /**
2827
+ * The longest step `tick` hands a frame, in seconds.
2828
+ *
2829
+ * @group Loop
2830
+ */
2831
+ declare const MAX_STEP = 0.1;
2832
+ /**
2833
+ * @group Loop
2834
+ * @category Important
2835
+ */
2836
+ type Loop = {
2837
+ /**
2838
+ * Whether the game may ask for frames now. Known as soon as `init` resolves.
2839
+ *
2840
+ * `false` means no `requestAnimationFrame`, no timers driving the game: what
2841
+ * was drawn last stays on screen.
2842
+ */
2843
+ readonly allowed: boolean;
2844
+ /**
2845
+ * Told whenever `allowed` changes. Return a promise to hold the host's reply.
2846
+ *
2847
+ * For a game whose engine runs its own loop: sleep it when this says `false`
2848
+ * and wake it when it says `true`.
2849
+ */
2850
+ on(listener: LoopListener): Unsubscribe;
2851
+ /**
2852
+ * Run `frame` once per display frame while the loop is `allowed`, and not at
2853
+ * all otherwise. What it drew last stays on screen meanwhile.
2854
+ *
2855
+ * A player's own pause is the game's: the loop keeps running so the game can
2856
+ * draw its pause screen, and the game stops advancing its own clock.
2857
+ *
2858
+ * Returns a function that stops this frame callback for good.
2859
+ */
2860
+ tick(frame: FrameCallback): Unsubscribe;
2861
+ };
2862
+ //#endregion
2863
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/panel.d.ts
2864
+ /**
2865
+ * Where the host's panel stands over the game.
2866
+ *
2867
+ * @group Panels
2868
+ */
2869
+ type PanelState = {
2870
+ /**
2871
+ * Whether the panel is over the game, on any page: the game's own, or one of
2872
+ * the host's, such as the player's own tab. Either way the player is reading
2873
+ * the panel rather than playing.
2874
+ */
2875
+ readonly isOpen: boolean;
2876
+ /**
2877
+ * Which of the game's pages it is open on, or `null`: closed, or open on a
2878
+ * page of the host's own that a game has no name for.
2879
+ */
2880
+ readonly page: PageId | null;
2881
+ };
2882
+ /**
2883
+ * The panel opened, closed or moved to another page.
2884
+ *
2885
+ * `previous` is where it stood before, so a listener can tell *closed* from
2886
+ * *moved* without keeping its own copy — a player going from Scores to Input
2887
+ * never passes through a closed panel, and a game that resumed on every change
2888
+ * would resume mid-read.
2889
+ *
2890
+ * @group Panels
2891
+ */
2892
+ type PanelListener = (now: PanelState, previous: PanelState) => void;
2893
+ /**
2894
+ * @group Panels
2895
+ * @category Important
2896
+ */
2897
+ type Panel = {
2898
+ /**
2899
+ * Whether this host will open its panel for this game at all: `pages` is not
2900
+ * empty.
2901
+ */
2902
+ readonly available: boolean;
2903
+ /**
2904
+ * The pages this host will open for this game, in the order it would show
2905
+ * them.
2906
+ *
2907
+ * Empty is a host with no panel and a host older than panels alike, which
2908
+ * are one fact from here: draw it yourself. It is per game as well as per
2909
+ * host — a game with no boards is not offered `scores`, because there is no
2910
+ * board for the host to draw. A page a host names that this SDK has no word
2911
+ * for is left out: a game cannot open what it cannot name.
2912
+ */
2913
+ readonly pages: readonly PageId[];
2914
+ /**
2915
+ * Whether the panel is over the game right now, on any page.
2916
+ *
2917
+ * Right from the first frame: a host does not close its own furniture to
2918
+ * start a game — the site's panel follows the focused game rather than
2919
+ * shutting on travel — so this is answered at the handshake rather than after
2920
+ * the first change.
2921
+ *
2922
+ * Reading it in a loop is free; it changes only when the host says so.
2923
+ */
2924
+ readonly isOpen: boolean;
2925
+ /**
2926
+ * Which of the game's pages the panel is open on, or `null`: closed, or open
2927
+ * on a page of the host's own.
2928
+ *
2929
+ * **Not restricted to `pages`.** A host may show a page of the game's that no
2930
+ * game was ever invited to open, and a game that ignored it because the name
2931
+ * was unfamiliar would keep playing under it.
2932
+ */
2933
+ readonly page: PageId | null;
2934
+ /**
2935
+ * Run this whenever the panel opens, closes or moves to another page.
2936
+ *
2937
+ * Returns a function that removes the listener. Whatever a listener throws
2938
+ * goes to `init`'s `onError` and the rest still run: one game's broken
2939
+ * handler must not stop the others hearing that the panel closed.
2940
+ *
2941
+ * **This is the callback, and `layout` is the mechanism.** A game that only
2942
+ * needs to move its buttons out of the way should keep using `layout` — the
2943
+ * rectangle is the answer to that question. This is for the decisions a
2944
+ * rectangle cannot support: ducking the music while they read, holding a run
2945
+ * rather than counting it against them.
2946
+ *
2947
+ * It is not a pause signal: the panel opens over a game that is still
2948
+ * `active`. A game that says it is `running` is asked to pause through
2949
+ * `pause` when the panel opens over it; for anything else, what to do about
2950
+ * it is the game's.
2951
+ */
2952
+ on(listener: PanelListener): Unsubscribe;
2953
+ /**
2954
+ * Ask the host to open its panel on one of its pages, in front of the player.
2955
+ *
2956
+ * Nothing to wait for: when the player closes it is not something to wait
2957
+ * on either, because a host is free to keep its panel docked beside the game
2958
+ * and never close it. What happens arrives through `on`.
2959
+ *
2960
+ * A page this host does not draw does nothing — a game checking `pages` first
2961
+ * never asks for one. Anything else that goes wrong is reported to `onError`:
2962
+ * `rate_limited` is a host that will not open it right now, asked too often
2963
+ * or asked to cover a player mid-run.
2964
+ *
2965
+ * **Call it from a press, and only from a press.** This puts something over
2966
+ * the game, and a host is within its rights to refuse one no player asked
2967
+ * for — the same rule a share is under, and for the same reason.
2968
+ */
2969
+ open(page: PageId): void;
2970
+ };
2971
+ //#endregion
2972
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/pause.d.ts
2973
+ /**
2974
+ * The host has asked for a pause, or for the game to carry on.
2975
+ *
2976
+ * **Return a promise and the host waits for it**, the way it waits on an
2977
+ * order: a game that has to finish an animation before it
2978
+ * can honestly say it has stopped holds the reply until it has. The leash is
2979
+ * short — half a second at most hosts — so slow is fine and hung is not.
2980
+ *
2981
+ * **Say where you ended up with `set`.** The reply the host receives is
2982
+ * whatever the state is once every listener has settled, so a listener that
2983
+ * pauses the game calls `set('paused')` and one that finds there is nothing to
2984
+ * pause does nothing at all: the state is already `unpausable`, and that is the
2985
+ * honest answer.
2986
+ *
2987
+ * @group Pause
2988
+ */
2989
+ type PauseListener = (paused: boolean) => void | Promise<void>;
2990
+ /**
2991
+ * @group Pause
2992
+ * @category Important
2993
+ */
2994
+ type Pause = {
2995
+ /**
2996
+ * Where this game currently stands, as the host has last been told.
2997
+ *
2998
+ * `unpausable` until the game says otherwise, which is what a game that never
2999
+ * mentions pausing means.
3000
+ */
3001
+ readonly state: PauseState;
3002
+ /**
3003
+ * Say where the game now stands.
3004
+ *
3005
+ * **The one way to change it, whoever's idea it was.** A player pressing the
3006
+ * game's own pause control, a run ending onto a title screen with nothing
3007
+ * left to stop, and a listener applying what the host just asked for all end
3008
+ * up here — so there is one place a game's pause state is written and one
3009
+ * message shape it reaches the host in.
3010
+ *
3011
+ * Told to the host immediately, unless it is what the host already believes:
3012
+ * saying the same word twice is not a change, and a host that redrew its
3013
+ * control on every frame of a game that calls this in its loop would be a
3014
+ * host flickering for nothing.
3015
+ *
3016
+ * Nothing is stopped by calling this. Freezing the clock, holding the
3017
+ * animation and quieting the sound are the game's own work, and this is how
3018
+ * the game says it has done it.
3019
+ */
3020
+ set(state: PauseState): void;
3021
+ /**
3022
+ * Told whenever the host asks for a pause or a resume: its own pause button,
3023
+ * its panel opening, or the game being taken away from the player mid-play.
3024
+ *
3025
+ * It hears the host asking, not the state moving — the state is whatever
3026
+ * the game says with `set` — so it is not called for a change the game made
3027
+ * itself: a listener that fired on the game's own `set` would be a game
3028
+ * hearing its own decision back, and one that answered it would be a loop.
3029
+ */
3030
+ on(listener: PauseListener): Unsubscribe;
3031
+ };
3032
+ //#endregion
3033
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/resources.d.ts
3034
+ /**
3035
+ * The host has let the game hold its resources again, or asked for them back.
3036
+ * Return a promise to make the host wait until they have gone, or come back.
3037
+ *
3038
+ * @group Resources
3039
+ */
3040
+ type ResourcesListener = (allowed: boolean) => void | Promise<void>;
3041
+ /**
3042
+ * @group Resources
3043
+ * @category Important
3044
+ */
3045
+ type Resources = {
3046
+ /**
3047
+ * Whether the game may hold what it could load again. Known as soon as
3048
+ * `init` resolves.
3049
+ *
3050
+ * `false` means let go of whatever is large and can be rebuilt — textures,
3051
+ * decoded sound, the level's geometry — and keep what cannot, such as the
3052
+ * state of play.
3053
+ */
3054
+ readonly allowed: boolean;
3055
+ /**
3056
+ * Told whenever `allowed` changes. Return a promise to hold the host's
3057
+ * reply: the host waits, briefly, for the memory to have gone before it
3058
+ * warms the next game.
3059
+ */
3060
+ on(listener: ResourcesListener): Unsubscribe;
3061
+ };
3062
+ //#endregion
3063
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/save.d.ts
3064
+ /**
3065
+ * What a game gets back from a read: its own schema number, and its state.
3066
+ *
3067
+ * @group Save
3068
+ * @category Useful
3069
+ */
3070
+ type SaveState = {
3071
+ /** Whatever the game passed to `write`, or 1 where it never named one. */
3072
+ version: number;
3073
+ /** Parsed where it was written as a value, verbatim where it was a string. */
3074
+ state: unknown;
3075
+ };
3076
+ /**
3077
+ * What is stored, as far as anybody has said.
3078
+ *
3079
+ * `known: false` is nobody having said yet — a host whose store had not
3080
+ * answered by the handshake — and the only way to find out is to `read()`.
3081
+ * `known: true` carries the answer: a header (how big, how old, what schema),
3082
+ * or `null` for a player with nothing stored. The two are kept apart by a
3083
+ * field a game has to look at, rather than by `null` against `undefined`.
3084
+ *
3085
+ * @group Save
3086
+ * @category Useful
3087
+ */
3088
+ type SaveStatus = {
3089
+ readonly known: false;
3090
+ } | {
3091
+ readonly known: true;
3092
+ readonly stored: SaveStored | null;
3093
+ };
3094
+ /**
3095
+ * Told when what is stored changes: a read answered what was unknown, or the
3096
+ * game wrote or cleared its save.
3097
+ *
3098
+ * @group Save
3099
+ * @category Useful
3100
+ */
3101
+ type SaveListener = (now: SaveStatus, previous: SaveStatus) => void;
3102
+ /**
3103
+ * @group Save
3104
+ * @category Important
3105
+ */
3106
+ type Save = {
3107
+ /**
3108
+ * Whether this host keeps saves at all. Known at the handshake: a game with
3109
+ * none leaves out *Continue* rather than learning on the press.
3110
+ */
3111
+ readonly available: boolean;
3112
+ /**
3113
+ * What is stored, before anything has been fetched.
3114
+ *
3115
+ * Enough to draw *Continue* or *New game* before asking for anything, and
3116
+ * enough for a game now on schema 3 that will not migrate a 1 to never fetch
3117
+ * it at all. It follows the game's own reads, writes and clears.
3118
+ */
3119
+ readonly status: SaveStatus;
3120
+ /** Told whenever `status` changes. */
3121
+ on(listener: SaveListener): Unsubscribe;
3122
+ /**
3123
+ * Fetch the state.
3124
+ *
3125
+ * `null` for a player with nothing stored. Rejects with a `BridgeError` where
3126
+ * the host refused — `unsupported` from a host with no store — which is a
3127
+ * game's cue to carry on without one rather than to fail to load.
3128
+ */
3129
+ read(): Promise<SaveState | null>;
3130
+ /**
3131
+ * Keep this. Resolves when the host says the record is durable.
3132
+ *
3133
+ * A string is stored as it stands and comes back a string; anything else is
3134
+ * `JSON.stringify`d and comes back parsed. A value that cannot survive that
3135
+ * at all — a cycle, a `BigInt` — rejects with `bad_params` before anything
3136
+ * reaches the bridge.
3137
+ *
3138
+ * `version` is the game's own schema number and means whatever the game says
3139
+ * it means. There is no migration hook: a hook would be frozen game-side code
3140
+ * that is sugar over an `if`, and a game that got it wrong could never be
3141
+ * fixed.
3142
+ */
3143
+ write(state: unknown, options?: {
3144
+ version?: number;
3145
+ }): Promise<void>;
3146
+ /** Throw the save away — a game resetting its own progress, at a player's asking. */
3147
+ clear(): Promise<void>;
3148
+ /** The largest save this host will take, in bytes. `SAVE_BYTES` where it said nothing. */
3149
+ readonly maxBytes: number;
3150
+ };
3151
+ //#endregion
3152
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/score.d.ts
3153
+ /**
3154
+ * Where this player stands on every board, as far as anybody has said.
3155
+ *
3156
+ * `known: false` is this host not having an answer yet — a store still
3157
+ * replying, a client that cannot reach its server — and a game that drew "no
3158
+ * best yet" for it would be telling somebody their record was gone because a
3159
+ * network was slow. `known: true` carries every board this host takes, each
3160
+ * with the player's standing on it or `null` for a board they have never
3161
+ * scored on.
3162
+ *
3163
+ * @group Score
3164
+ * @category Useful
3165
+ */
3166
+ type ScoreStandings<Board extends string = string> = {
3167
+ readonly known: false;
3168
+ } | {
3169
+ readonly known: true;
3170
+ readonly boards: Readonly<Record<Board, ScoreStanding | null>>;
3171
+ };
3172
+ /**
3173
+ * Told when the standing changes: the host's answer arrived after the
3174
+ * handshake, or a submission moved it.
3175
+ *
3176
+ * @group Score
3177
+ * @category Useful
3178
+ */
3179
+ type ScoreListener<Board extends string = string> = (now: ScoreStandings<Board>, previous: ScoreStandings<Board>) => void;
3180
+ /**
3181
+ * @group Score
3182
+ * @category Important
3183
+ */
3184
+ type Score<Board extends string = string> = {
3185
+ /**
3186
+ * Whether this host keeps scores at all. Known at the handshake: a game with
3187
+ * none leaves out its score screen rather than learning on the first
3188
+ * submission.
3189
+ */
3190
+ readonly available: boolean;
3191
+ /**
3192
+ * The boards this host will take a score on, from the manifest it holds.
3193
+ * Empty where it keeps none.
3194
+ */
3195
+ readonly boards: readonly Board[];
3196
+ /** Where this player stands on each board. */
3197
+ readonly standing: ScoreStandings<Board>;
3198
+ /** Told whenever `standing` changes. */
3199
+ on(listener: ScoreListener<Board>): Unsubscribe;
3200
+ /**
3201
+ * Keep this number, and tell me what it did.
3202
+ *
3203
+ * Resolves with `improved` — whether this run beat the player's own best,
3204
+ * where a tie is not an improvement — and `best`, what that best now is.
3205
+ * `rank` and `of` come too when the host happens to know them, and a game
3206
+ * that draws a position must handle their being absent: a host that
3207
+ * acknowledges a score as soon as it is *durable* has not necessarily heard
3208
+ * back from anywhere yet.
3209
+ *
3210
+ * `board` may be left out by a game with one board, which is what a game with
3211
+ * one board should do. A game with several that leaves it out is refused
3212
+ * rather than guessed at.
3213
+ *
3214
+ * Rejects with `unsupported` from a host that keeps no scores. `bad_params`
3215
+ * is a board this game's manifest never declared, which is an author's
3216
+ * mistake and not a player's. `rate_limited` means this is being called far
3217
+ * more often than runs actually end.
3218
+ *
3219
+ * **Every run is its own submission.** A worse run than last time is not an
3220
+ * error and is not to be withheld — the host keeps it, `improved` comes back
3221
+ * `false`, and the best stands. A game that only submits its personal bests
3222
+ * is a game whose weekly board has one number in it.
3223
+ */
3224
+ submit(value: number, options?: {
3225
+ board?: Board;
3226
+ }): Promise<ScoreResult>;
3227
+ };
3228
+ //#endregion
3229
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/share.d.ts
3230
+ /**
3231
+ * @group Share
3232
+ * @category Important
3233
+ */
3234
+ type Share = {
3235
+ /**
3236
+ * Whether this host can put a link in front of the player. Known at the
3237
+ * handshake: draw a Share button only where it is `true`, rather than
3238
+ * learning on the press that there was nothing behind it.
3239
+ */
3240
+ readonly available: boolean;
3241
+ /**
3242
+ * What the link that opened this game carried, or `null` for a player who
3243
+ * arrived the ordinary way.
3244
+ *
3245
+ * Exactly the string some run of this game passed to `offer` — the board that
3246
+ * was sent, the seed that was beaten — and it is here at startup rather than
3247
+ * fetched, so a game opened from a link draws what the link says in its first
3248
+ * frame instead of drawing the ordinary game and jumping.
3249
+ *
3250
+ * **Read it the way you would read a stranger's input, because it is one.** A
3251
+ * URL is written by anybody: it can be truncated by a chat client, edited by
3252
+ * a curious player, or made up. This game wrote the format and this game must
3253
+ * check it — the same care `save.read` deserves, for a stronger reason.
3254
+ *
3255
+ * **`null` says nothing about whether a link can be offered.** It means
3256
+ * nobody arrived by one; `available` is what says whether one can be made.
3257
+ */
3258
+ readonly data: string | null;
3259
+ /**
3260
+ * Ask the host to put a link to this game in front of the player.
3261
+ *
3262
+ * Nothing to wait for. The host puts the link in front of the player — a
3263
+ * share sheet, the clipboard — and tells them what became of it, "link
3264
+ * copied" included; the game is not told, and has nothing to draw. A player
3265
+ * may keep a sheet open as long as they like, and pressing Share again while
3266
+ * one is up does not open a second.
3267
+ *
3268
+ * `data` is the game's own and travels in the link, opaque to the host, back
3269
+ * to whoever opens it. A string, because it has to survive being a URL and
3270
+ * because the game reading it back is the game that wrote it: anything
3271
+ * structured is the game's to encode and the game's to parse. `SHARE_BYTES`
3272
+ * is what every host carries; more than that is a host's own decision, and a
3273
+ * host that will not carry it refuses with `too_large`.
3274
+ *
3275
+ * `meta` is for the platform rather than for the game: why this link was made
3276
+ * — the level, the score, which of the game's buttons — kept beside the
3277
+ * host's own record of who made it and how, so a game's makers can learn
3278
+ * which of its shares bring people in. Anything JSON can say, and
3279
+ * `SHARE_META_BYTES` of it is what every host takes. Nobody who opens the link
3280
+ * is handed it.
3281
+ *
3282
+ * What goes wrong is reported to `onError` and sends nothing: `bad_params`
3283
+ * for a `data` that is not a string or a `meta` JSON cannot carry, which are
3284
+ * bugs in the game; `too_large` from a host for more than it carries;
3285
+ * `rate_limited` from one that will not offer a link right now. A host with
3286
+ * no link to give does nothing, which a game checking `available` never asks
3287
+ * of it. Every one of them is a game that carries on: nothing about a share
3288
+ * belongs to the state of play.
3289
+ *
3290
+ * **Call it from a press, and only from a press.** This opens something over
3291
+ * the game, and a host will be within its rights to refuse an offer no player
3292
+ * asked for — some cannot even honour one, because the permission to put a
3293
+ * sheet on screen comes from the player's own gesture.
3294
+ */
3295
+ offer(options?: {
3296
+ data?: string;
3297
+ meta?: ShareMeta;
3298
+ }): void;
3299
+ };
3300
+ //#endregion
3301
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/init.d.ts
3302
+ /**
3303
+ * What `init` hands back: every namespace a game has, on one object.
3304
+ *
3305
+ * `M` is the game's manifest type, `typeof config.manifest`: every name the
3306
+ * game passes in — an action, a board, an achievement, an event, a channel —
3307
+ * is then one its manifest declares. Without it, every name is a `string`.
3308
+ *
3309
+ * `Useful` rather than `Important` because of how it is reached. A reader comes
3310
+ * looking for `init` — it is the only way in — and arrives here by following
3311
+ * what it returns, which is the same path the code takes.
3312
+ *
3313
+ * @group Bridge
3314
+ * @category Useful
3315
+ */
3316
+ type GameSdk<M extends object = GameManifest> = {
3317
+ /**
3318
+ * How far the game has got: `loading`, `ready`, `disposed`. The one thing
3319
+ * here a game must actually call is `lifecycle.ready()`.
3320
+ *
3321
+ * @category Important
3322
+ */
3323
+ lifecycle: Lifecycle;
3324
+ /**
3325
+ * The game's frame loop, and the host's order about it: ask for frames only
3326
+ * while `loop.allowed`. `loop.tick()` runs a frame callback that obeys it on
3327
+ * the game's behalf.
3328
+ *
3329
+ * @category Important
3330
+ */
3331
+ loop: Loop;
3332
+ /**
3333
+ * The host's order about memory: hold what can be loaded again only while
3334
+ * `resources.allowed`, and let it go when told.
3335
+ *
3336
+ * @category Important
3337
+ */
3338
+ resources: Resources;
3339
+ /**
3340
+ * What the host has put over the frame, how big that frame is, and what the
3341
+ * two leave the game. Numbers only: what to do about them is the game's, and
3342
+ * so is whatever it draws with.
3343
+ *
3344
+ * @category Useful
3345
+ */
3346
+ layout: Layout;
3347
+ /**
3348
+ * Who this run is for and what it is running on: the host, the game as the
3349
+ * host loaded it, the player, the machine.
3350
+ *
3351
+ * Settled at the handshake and frozen. There is nothing to listen to because
3352
+ * there is nothing that changes — which is also why reading it in a loop is
3353
+ * free, and why a game can branch on the device tier before it fetches its
3354
+ * first asset.
3355
+ *
3356
+ * @category Useful
3357
+ */
3358
+ info: Info;
3359
+ /**
3360
+ * Say what happened, for the host to keep.
3361
+ *
3362
+ * One way and unacknowledged: nothing here can be waited on, and nothing here
3363
+ * can fail. What the host does with an event — store it, show it in a log,
3364
+ * throw it away — is no business of the game's.
3365
+ *
3366
+ * @category Useful
3367
+ */
3368
+ analytics: Analytics<EventProps<M>>;
3369
+ /**
3370
+ * The one thing a game may keep: one document, this player's, this game's.
3371
+ *
3372
+ * Read it while you load and write it as you play. `save.status` says what
3373
+ * is waiting before anything is fetched, so a game can draw *Continue*
3374
+ * without a round trip; `read()` fetches it, `write()` resolves when the host
3375
+ * says the record is durable, and there is nothing to flush because nothing
3376
+ * is held here.
3377
+ *
3378
+ * `save.available` is `false` on a host with no store, whose calls answer
3379
+ * `unsupported`: a game with no save rather than one that fails to load.
3380
+ *
3381
+ * @category Useful
3382
+ */
3383
+ save: Save;
3384
+ /**
3385
+ * Keep a number against this player, and be told what it did.
3386
+ *
3387
+ * `score.standing` says where they already are before anything is asked
3388
+ * for, so a game draws a target in its first frame; `submit()` resolves with
3389
+ * whether the run beat it. What a game never gets is anybody else's number —
3390
+ * a board is drawn by the client over the game, not inside it.
3391
+ *
3392
+ * `score.available` is `false` on a host with no scoring, whose calls answer
3393
+ * `unsupported`: a game that carries on without a target.
3394
+ *
3395
+ * @category Useful
3396
+ */
3397
+ score: Score<BoardName<M>>;
3398
+ /**
3399
+ * What this player has been shown to have done, and how to say they have
3400
+ * done more.
3401
+ *
3402
+ * `achievements.standing` answers before anything is asked for, so a game
3403
+ * draws *3 of 12* in its first frame; `unlock()` says something is theirs
3404
+ * and `progress()` says how far along they are — as an absolute value, so
3405
+ * reporting it twice does nothing. What a game never gets is anybody else's:
3406
+ * the panel that lists them is drawn by the client over the game, not inside
3407
+ * it.
3408
+ *
3409
+ * `achievements.available` is `false` on a host with none, and reporting to
3410
+ * it does nothing: a game that carries on without a banner.
3411
+ *
3412
+ * @category Useful
3413
+ */
3414
+ achievements: Achievements<AchievementName<M>, MeasuredName<M>>;
3415
+ /**
3416
+ * What the player presses, how far they are pushing a stick, where the
3417
+ * pointer is — and the on-screen controls to draw where there is no keyboard.
3418
+ *
3419
+ * Read in the frame and answered in the frame: nothing about a press crosses
3420
+ * the bridge, and the controls are drawn here so a thumb on a stick reaches
3421
+ * the game in the frame it moved. What the host contributes is what the frame
3422
+ * cannot see — what this player remapped, what they are holding, and whether
3423
+ * on-screen controls mean anything on this device.
3424
+ *
3425
+ * The actions and the on-screen layouts are the ones the host read from the
3426
+ * published manifest, sent at the handshake: a game declares them in its
3427
+ * manifest and nowhere else. A host older than input sends none, and a game
3428
+ * there has nothing to bind.
3429
+ *
3430
+ * @category Useful
3431
+ */
3432
+ input: Input<ActionName<M>, LayoutName<M>>;
3433
+ /**
3434
+ * How loud this game may be, which the host decides and the game obeys.
3435
+ *
3436
+ * **The one namespace here that is not a service and cannot be refused.**
3437
+ * Everything else is a game asking a host for something; this is a host
3438
+ * telling a game something, and respecting it is the condition of being on
3439
+ * the platform rather than a courtesy. `output()` and `media()` obey it on
3440
+ * the game's behalf — the volume, mute, and silence whenever sound is not
3441
+ * `allowed`. `volume(channel)` is the number to multiply a sound by, for a
3442
+ * game whose engine mixes for itself.
3443
+ *
3444
+ * A host older than audio, or one with no volume controls, leaves this at
3445
+ * full and never pushes: the game plays the mix it meant, which is the only
3446
+ * sensible reading of silence here.
3447
+ *
3448
+ * @category Useful
3449
+ */
3450
+ audio: Audio<ChannelName<M>>;
3451
+ /**
3452
+ * Ask the host to offer the player a link to this game, carrying whatever the
3453
+ * game wants carried.
3454
+ *
3455
+ * The game names no URL and is told none: where this game lives is the host's
3456
+ * fact, and putting a link in front of a player — a sheet, the clipboard — is
3457
+ * something only the host can do from outside the frame. `share.available`
3458
+ * says at the handshake whether this host has a link to give.
3459
+ *
3460
+ * `share.data` is the way back in: what the link this run was opened with
3461
+ * carried, known at the handshake so a game can draw it in its first frame.
3462
+ *
3463
+ * @category Useful
3464
+ */
3465
+ share: Share;
3466
+ /**
3467
+ * Ask the host to open its panel on one of its pages: what this game is,
3468
+ * the board, the list of what has been unlocked, the page where a key is
3469
+ * rebound.
3470
+ *
3471
+ * The host draws all of it and the game supplies the moment — a pause screen,
3472
+ * the end of a run, the fourth wrong key. Nothing here says where the panel
3473
+ * goes or what it looks like: a page is a subject, and the shape of one is
3474
+ * the host's.
3475
+ *
3476
+ * `panel.pages` is what a game draws its own furniture from, known at the
3477
+ * handshake: a game that shows *Press ⚙ to rebind* when `input` is there
3478
+ * draws its own rebinding screen when it is not, and it decides that before
3479
+ * its first frame rather than when the player presses.
3480
+ *
3481
+ * `panel.isOpen`, `panel.page` and `panel.on` are the other direction — the
3482
+ * panel is over the game now, whoever opened it, and on which page. Higher
3483
+ * level than the `overlay` rectangle in `layout`, which is still the right
3484
+ * answer for moving buttons out of the way: this one says it is the panel,
3485
+ * and which page, so a game can duck its music while a player rebinds a key
3486
+ * rather than guessing from a box.
3487
+ *
3488
+ * Pausing is `pause` below, and the two are worth keeping apart: a game that
3489
+ * says it is `running` is asked to pause when the panel opens over it, and
3490
+ * that request arrives there rather than here.
3491
+ *
3492
+ * @category Useful
3493
+ */
3494
+ panel: Panel;
3495
+ /**
3496
+ * Whether the game is holding still, which the host may ask for and only the
3497
+ * game can answer.
3498
+ *
3499
+ * **The one namespace where a host's request may honestly come back saying
3500
+ * something else.** A game with nothing on the field to stop — a title
3501
+ * screen, the gap between rounds — answers `unpausable`, and that is a state
3502
+ * the host draws rather than a failure it recovers from: it shows no control
3503
+ * at all, because a disabled button is a promise that pressing it would do
3504
+ * something.
3505
+ *
3506
+ * `on()` is where the stopping goes, and the host waits for it the way it
3507
+ * waits for a fade-out. The host asks when its pause button is pressed, when
3508
+ * its panel opens, and when it takes a game in play away from the player. `set()` is how the game says where it ended up —
3509
+ * including when nobody asked, which is a player pressing the game's own
3510
+ * pause key.
3511
+ *
3512
+ * Nothing here stops anything by itself: what freezes and what carries on is
3513
+ * a thousand decisions this SDK has no business making. A game that never
3514
+ * calls `set` stays `unpausable`, which is what a host is told about every
3515
+ * game published before pausing existed.
3516
+ *
3517
+ * @category Useful
3518
+ */
3519
+ pause: Pause;
3520
+ /**
3521
+ * Ask the host to answer, and time how long it takes.
3522
+ *
3523
+ * Resolves with the round trip in milliseconds, rejecting with a `BridgeError`
3524
+ * whose code is `timeout` if the host never answers. This is the liveness
3525
+ * check the demo is built on, and it stays useful afterwards.
3526
+ */
3527
+ ping(): Promise<number>;
3528
+ /** Tear the bridge down. Calls in flight reject with `disposed`. */
3529
+ dispose(): void;
3530
+ };
3531
+ /**
3532
+ * @group Bridge
3533
+ * @category Useful
3534
+ */
3535
+ type InitOptions = {
3536
+ /** The window the game is running in. Defaults to the global one. */
3537
+ scope?: Window | undefined;
3538
+ /**
3539
+ * How long a call waits before rejecting with `timeout`.
3540
+ *
3541
+ * Never shorter for the handshake than ten seconds, whatever this says. A
3542
+ * game that gives up on a hello the host has heard is one the host goes on to
3543
+ * show anyway, once it tires of waiting for `ready` — with nothing running
3544
+ * in it.
3545
+ */
3546
+ timeoutMs?: number | undefined;
3547
+ /**
3548
+ * The channel to talk over. Defaults to whatever the game turns out to be
3549
+ * running in — a shim a native shell left, or the frame that embedded us —
3550
+ * which is what every real game wants; a test, or a host that embeds a game
3551
+ * some other way, can hand over its own.
3552
+ */
3553
+ transport?: Transport | undefined;
3554
+ /**
3555
+ * How long to wait for a host to turn up when there is neither a shim nor a
3556
+ * parent frame, before rejecting. Defaults to `SHIM_WAIT_MS`.
3557
+ *
3558
+ * Here from day one rather than when a platform first needs it: this is
3559
+ * game-side code, bundled into a game at build time, so it can never be added
3560
+ * later for games already published.
3561
+ */
3562
+ shimWaitMs?: number | undefined;
3563
+ /**
3564
+ * Where a failure that belongs to nobody's promise goes — a lifecycle
3565
+ * listener that threw, an event the SDK refused. Defaults to `console.error`.
3566
+ *
3567
+ * The host is told about each of these as well, whatever this does with
3568
+ * them, as it is about any error the game throws and nothing catches.
3569
+ */
3570
+ onError?: ((error: unknown) => void) | undefined;
3571
+ };
3572
+ /**
3573
+ * Introduce the game to its host, and hand back the SDK once it has answered.
3574
+ *
3575
+ * The handshake is not optional and `init` does not resolve without it. It
3576
+ * settles the protocol version both sides will speak, and gets this game a
3577
+ * name to stamp its requests with — without which a host cannot tell what a
3578
+ * reloaded game says from what the game before it said.
3579
+ *
3580
+ * **Let a rejection through rather than catching it.** Every way this fails
3581
+ * leaves the game with no host to talk to — none there, one that never
3582
+ * answered, one too old or too new to speak to it — and a host does not show a
3583
+ * game that never got through the handshake. Nobody is left to read a message
3584
+ * drawn into the page; a developer reads the console, which is where an
3585
+ * uncaught rejection goes, with its stack. Nor is there a way to carry on
3586
+ * without a host: that is every namespace written twice, and the half no host
3587
+ * ever runs is the half that breaks.
3588
+ *
3589
+ * Calling it twice in one game is a bug: the host only ever talks to the most
3590
+ * recent one, and the abandoned peer's calls start failing with `stale`.
3591
+ *
3592
+ * **The window is sized by the time it resolves.** `innerWidth` and
3593
+ * `innerHeight` agree with `layout.frame` from then on, so a game sizes itself
3594
+ * the way any page does.
3595
+ *
3596
+ * Pass the manifest's type to have every declared name checked:
3597
+ *
3598
+ * import type config from '../hopple.config.ts';
3599
+ * const hopple = await init<typeof config.manifest>();
3600
+ *
3601
+ * @group Bridge
3602
+ * @category Important
3603
+ */
3604
+ declare function init<M extends object = GameManifest>(options?: InitOptions): Promise<GameSdk<M>>;
3605
+ //#endregion
3606
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/transport.d.ts
3607
+ /**
3608
+ * How long a game with no shim and no parent waits for one to turn up.
3609
+ *
3610
+ * Generous on purpose: a shim two seconds late is not coming, and the only game
3611
+ * made to wait is one that was never going to run.
3612
+ *
3613
+ * @group Bridge
3614
+ */
3615
+ declare const SHIM_WAIT_MS = 2000;
3616
+ /** @group Bridge */
3617
+ type HostTransportOptions = {
3618
+ /** The window the game is running in. Defaults to the global one. */
3619
+ scope?: Window | undefined;
3620
+ /** How long to wait for a late shim before giving up. */
3621
+ shimWaitMs?: number | undefined;
3622
+ /** Where a shim that arrived wrongly, or twice, is reported. */
3623
+ onError?: ((error: unknown) => void) | undefined;
3624
+ };
3625
+ /**
3626
+ * Find something to talk over, waiting briefly if there is nothing yet.
3627
+ *
3628
+ * Asynchronous because of that last case alone. The two ordinary ones — a shim
3629
+ * already installed, a parent frame — resolve without yielding a turn.
3630
+ *
3631
+ * @group Bridge
3632
+ * @category Useful
3633
+ */
3634
+ declare function hostTransport(options?: HostTransportOptions): Promise<Transport>;
3635
+ //#endregion
3636
+ //#region ../../../../../../../tmp/hopple-sdk-types-XAG7tD/game/src/shim.d.ts
3637
+ /**
3638
+ * Whether an arbitrary value is a shim this SDK knows how to speak to.
3639
+ *
3640
+ * A version we do not know is a shim we must not guess at — a `v: 2` that moved
3641
+ * `send` to a different signature would be a channel we corrupt rather than one
3642
+ * we fail on. Refusing it falls through to the frame branch, and then to the
3643
+ * wait, which either connects or says clearly that nothing did.
3644
+ *
3645
+ * @group Bridge
3646
+ */
3647
+ declare function isShim(value: unknown): value is HoppleShim;
3648
+ /**
3649
+ * What a platform left for us, or nothing — in which case we are in a frame.
3650
+ *
3651
+ * @group Bridge
3652
+ */
3653
+ declare function findShim(scope: Window): HoppleShim | null;
3654
+ /**
3655
+ * A shim, seen by the peer as an ordinary channel.
3656
+ *
3657
+ * The trust boundary is in here, in SDK code, and never in the platform's: a
3658
+ * shim that cannot validate is a shim that cannot validate wrongly.
3659
+ *
3660
+ * @group Bridge
3661
+ */
3662
+ declare function shimTransport(shim: HoppleShim): Transport;
3663
+ //#endregion
3664
+ 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 };