@hopple/sdk 0.1.0-rc.3 → 1.0.0-next.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -27
- package/config.schema.json +261 -41
- package/index.d.ts +578 -879
- package/index.js +2 -29
- package/manifest.d.ts +1063 -0
- package/manifest.js +896 -0
- package/manifest.md +101 -11
- package/package.json +9 -1
package/config.schema.json
CHANGED
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
},
|
|
22
22
|
"page": {
|
|
23
23
|
"type": "string",
|
|
24
|
-
"description": "The built page, relative to the config
|
|
25
|
-
"markdownDescription": "The built page, relative to the config
|
|
24
|
+
"description": "The built page, relative to the config. The directory it is in is published, with the page as its `index.html`; a page beside its config is published alone, so it has to hold the whole game.",
|
|
25
|
+
"markdownDescription": "The built page, relative to the config. The directory it is in is\npublished, with the page as its `index.html`; a page beside its config is\npublished alone, so it has to hold the whole game.",
|
|
26
26
|
"pattern": "\\.[Hh][Tt][Mm][Ll]?$",
|
|
27
27
|
"errorMessage": "must be the built .html file, like \"dist/index.html\""
|
|
28
28
|
},
|
|
29
29
|
"manifest": {
|
|
30
|
-
"$ref": "#/definitions/
|
|
30
|
+
"$ref": "#/definitions/ProjectManifest",
|
|
31
31
|
"description": "What the game is. Uploaded beside the page, and read by every host before the game runs.",
|
|
32
32
|
"markdownDescription": "What the game is. Uploaded beside the page, and read by every host before the game runs."
|
|
33
33
|
}
|
|
@@ -37,15 +37,15 @@
|
|
|
37
37
|
"manifest"
|
|
38
38
|
],
|
|
39
39
|
"additionalProperties": false,
|
|
40
|
-
"description": "A game project's hopple config: `hopple.config.ts`, `hopple.config.js` or `hopple.config.json`, beside the project's `package.json`.\n\nEverything the `hopple` command line needs to know about a project, in one place: how to build the game, which built file is the game, and the manifest that says what the game is. Only the manifest leaves the machine, uploaded beside the page as `game.manifest.json`. A TypeScript or JavaScript config default-exports one of these; a JSON config is one.\n\n```ts import type { HoppleConfig } from '@hopple/sdk';\n\nexport default { build: 'npm run build', page: 'dist/index.html', manifest: {
|
|
41
|
-
"markdownDescription": "A game project's hopple config: `hopple.config.ts`, `hopple.config.js` or\n`hopple.config.json`, beside the project's `package.json`.\n\nEverything the `hopple` command line needs to know about a project, in one\nplace: how to build the game, which built file is the game, and the manifest\nthat says what the game is. Only the manifest leaves the machine, uploaded\nbeside the page as `game.manifest.json`. A TypeScript or JavaScript config\ndefault-exports one of these; a JSON config is one.\n\n```ts\nimport type { HoppleConfig } from '@hopple/sdk';\n\nexport default {\n build: 'npm run build',\n page: 'dist/index.html',\n manifest: {
|
|
40
|
+
"description": "A game project's hopple config: `hopple.config.ts`, `hopple.config.js` or `hopple.config.json`, beside the project's `package.json`.\n\nEverything the `hopple` command line needs to know about a project, in one place: how to build the game, which built file is the game, and the manifest that says what the game is. Only the manifest leaves the machine, uploaded beside the page as `game.manifest.json`. A TypeScript or JavaScript config default-exports one of these; a JSON config is one.\n\n```ts import type { HoppleConfig } from '@hopple/sdk';\n\nexport default { build: 'npm run build', page: 'dist/index.html', manifest: { id: 'my-game', version: '0.1.0', title: 'My Game', orientation: 'portrait', audio: null }, } satisfies HoppleConfig; ```",
|
|
41
|
+
"markdownDescription": "A game project's hopple config: `hopple.config.ts`, `hopple.config.js` or\n`hopple.config.json`, beside the project's `package.json`.\n\nEverything the `hopple` command line needs to know about a project, in one\nplace: how to build the game, which built file is the game, and the manifest\nthat says what the game is. Only the manifest leaves the machine, uploaded\nbeside the page as `game.manifest.json`. A TypeScript or JavaScript config\ndefault-exports one of these; a JSON config is one.\n\n```ts\nimport type { HoppleConfig } from '@hopple/sdk';\n\nexport default {\n build: 'npm run build',\n page: 'dist/index.html',\n manifest: { id: 'my-game', version: '0.1.0', title: 'My Game', orientation: 'portrait', audio: null },\n} satisfies HoppleConfig;\n```"
|
|
42
42
|
},
|
|
43
|
-
"
|
|
43
|
+
"ProjectManifest": {
|
|
44
44
|
"type": "object",
|
|
45
|
+
"additionalProperties": false,
|
|
45
46
|
"properties": {
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"const": 1
|
|
47
|
+
"api": {
|
|
48
|
+
"$ref": "#/definitions/ApiVersion"
|
|
49
49
|
},
|
|
50
50
|
"id": {
|
|
51
51
|
"type": "string",
|
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
"maxLength": 64,
|
|
64
64
|
"errorMessage": "must be a handle: lowercase letters, digits and hyphens"
|
|
65
65
|
},
|
|
66
|
+
"publishable": {
|
|
67
|
+
"type": "boolean",
|
|
68
|
+
"description": "`false` for a game that is never to be uploaded: an example, a test, a game kept to one machine. `hopple dev` and `hopple check` still run it; `hopple upload` and `hopple publish` refuse it, and so does a server it is sent to another way. Absent, it may be published.",
|
|
69
|
+
"markdownDescription": "`false` for a game that is never to be uploaded: an example, a test, a\ngame kept to one machine. `hopple dev` and `hopple check` still run it;\n`hopple upload` and `hopple publish` refuse it, and so does a server it\nis sent to another way. Absent, it may be published."
|
|
70
|
+
},
|
|
66
71
|
"version": {
|
|
67
72
|
"type": "string",
|
|
68
73
|
"description": "The game's own version, `MAJOR.MINOR.PATCH`. Ours to read, never to parse for meaning.",
|
|
@@ -84,6 +89,16 @@
|
|
|
84
89
|
"maxLength": 500,
|
|
85
90
|
"errorMessage": "must not be blank"
|
|
86
91
|
},
|
|
92
|
+
"genres": {
|
|
93
|
+
"type": "array",
|
|
94
|
+
"items": {
|
|
95
|
+
"$ref": "#/definitions/GameGenre"
|
|
96
|
+
},
|
|
97
|
+
"description": "What kind of game it is, the main one first. Read by whoever files it in a catalogue, which never runs it. At most `GENRE_MAX`, not counting `local-multiplayer` and `online-multiplayer`: a game that is every kind tells a player nothing. The publisher's claim, taken at its word, except for those two. See `GameGenre`.",
|
|
98
|
+
"markdownDescription": "What kind of game it is, the main one first. Read by whoever files it in a\ncatalogue, which never runs it. At most `GENRE_MAX`, not counting\n`local-multiplayer` and `online-multiplayer`: a game that is every kind\ntells a player nothing. The publisher's claim, taken at its word, except\nfor those two. See `GameGenre`.",
|
|
99
|
+
"minItems": 1,
|
|
100
|
+
"maxItems": 5
|
|
101
|
+
},
|
|
87
102
|
"cover": {
|
|
88
103
|
"type": "string",
|
|
89
104
|
"description": "A picture of the game, shown wherever a link to it is pasted — a chat, a post, a message.\n\nThe path of a PNG, JPEG or WebP file, relative to the config the manifest is in, and at most `COVER_MAX_BYTES`. `hopple publish` uploads it beside the game. 1200 × 630 is the shape every platform shows whole; without one, a link shows Hopple's own card.",
|
|
@@ -95,13 +110,6 @@
|
|
|
95
110
|
"orientation": {
|
|
96
111
|
"$ref": "#/definitions/Orientation"
|
|
97
112
|
},
|
|
98
|
-
"sdk": {
|
|
99
|
-
"type": "string",
|
|
100
|
-
"description": "The `@hopple/sdk` version the game was built against, optionally prefixed with `^` or `~`.\n\nRecorded rather than enforced: what the host and the game actually have to agree on is the protocol version, which the handshake settles. This is for diagnostics — \"every game that broke was built against 2.3\" is a question worth being able to ask. Deliberately far narrower than everything npm accepts: a grammar we can read in one line is a grammar three packages can agree on.",
|
|
101
|
-
"markdownDescription": "The `@hopple/sdk` version the game was built against, optionally prefixed\nwith `^` or `~`.\n\nRecorded rather than enforced: what the host and the game actually have to\nagree on is the protocol version, which the handshake settles. This is for\ndiagnostics — \"every game that broke was built against 2.3\" is a question\nworth being able to ask. Deliberately far narrower than everything npm\naccepts: a grammar we can read in one line is a grammar three packages can\nagree on.",
|
|
102
|
-
"pattern": "^[\\^~]?\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$",
|
|
103
|
-
"errorMessage": "must be a version like 1.0.0, ^1.0.0 or ~1.0.0"
|
|
104
|
-
},
|
|
105
113
|
"layout": {
|
|
106
114
|
"$ref": "#/definitions/LayoutDeclaration",
|
|
107
115
|
"description": "How the game wants the host's furniture handled. See `InsetMode`.",
|
|
@@ -132,6 +140,11 @@
|
|
|
132
140
|
"description": "How this game is played, and what it draws to be played with. See `InputDeclaration`.",
|
|
133
141
|
"markdownDescription": "How this game is played, and what it draws to be played with. See `InputDeclaration`."
|
|
134
142
|
},
|
|
143
|
+
"players": {
|
|
144
|
+
"$ref": "#/definitions/PlayersDeclaration",
|
|
145
|
+
"description": "How many play on one device. See `PlayersDeclaration`.",
|
|
146
|
+
"markdownDescription": "How many play on one device. See `PlayersDeclaration`."
|
|
147
|
+
},
|
|
135
148
|
"audio": {
|
|
136
149
|
"anyOf": [
|
|
137
150
|
{
|
|
@@ -143,20 +156,57 @@
|
|
|
143
156
|
],
|
|
144
157
|
"description": "What this game makes noise with, one channel at a time, or `null` for a game that makes none. See `AudioDeclaration`.",
|
|
145
158
|
"markdownDescription": "What this game makes noise with, one channel at a time, or `null` for a\ngame that makes none. See `AudioDeclaration`."
|
|
159
|
+
},
|
|
160
|
+
"previews": {
|
|
161
|
+
"$ref": "#/definitions/PreviewsDeclaration",
|
|
162
|
+
"description": "The previews this game is written against, each at its revision. See `PreviewsDeclaration`.",
|
|
163
|
+
"markdownDescription": "The previews this game is written against, each at its revision. See\n`PreviewsDeclaration`."
|
|
146
164
|
}
|
|
147
165
|
},
|
|
148
166
|
"required": [
|
|
149
|
-
"
|
|
167
|
+
"audio",
|
|
150
168
|
"id",
|
|
151
|
-
"version",
|
|
152
|
-
"title",
|
|
153
169
|
"orientation",
|
|
154
|
-
"
|
|
155
|
-
"
|
|
170
|
+
"title",
|
|
171
|
+
"version"
|
|
172
|
+
],
|
|
173
|
+
"description": "The manifest as a project's config holds it: `api` may be left out, for the CLI to fill from the installed `@hopple/sdk`.",
|
|
174
|
+
"markdownDescription": "The manifest as a project's config holds it: `api` may be left out, for the\nCLI to fill from the installed `@hopple/sdk`."
|
|
175
|
+
},
|
|
176
|
+
"ApiVersion": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"description": "The API a game is written against: `M.m`, a major and the least minor it needs, or `next`, the pre-releases of the next stable release.",
|
|
179
|
+
"markdownDescription": "The API a game is written against: `M.m`, a major and the least minor it\nneeds, or `next`, the pre-releases of the next stable release.",
|
|
180
|
+
"pattern": "^(?:(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)|next)$",
|
|
181
|
+
"errorMessage": "must be the API the game is written against, like \"1.0\", or \"next\""
|
|
182
|
+
},
|
|
183
|
+
"GameGenre": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"enum": [
|
|
186
|
+
"racing",
|
|
187
|
+
"shooter",
|
|
188
|
+
"platformer",
|
|
189
|
+
"puzzle",
|
|
190
|
+
"arcade",
|
|
191
|
+
"physics",
|
|
192
|
+
"roguelike",
|
|
193
|
+
"strategy",
|
|
194
|
+
"building",
|
|
195
|
+
"idle",
|
|
196
|
+
"sports",
|
|
197
|
+
"horror",
|
|
198
|
+
"stealth",
|
|
199
|
+
"fighting",
|
|
200
|
+
"casual",
|
|
201
|
+
"simulation",
|
|
202
|
+
"adventure",
|
|
203
|
+
"rhythm",
|
|
204
|
+
"rpg",
|
|
205
|
+
"local-multiplayer",
|
|
206
|
+
"online-multiplayer"
|
|
156
207
|
],
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"markdownDescription": "The index signature is the additive-only rule applied to this file: a game\nbuilt against a newer SDK declares keys an older host has never heard of, and\nmust still run there. It costs a typo in a manifest literal going unnoticed\nby the compiler, which the validator catches instead."
|
|
208
|
+
"description": "What kind of game this is, as one word from a list every game shares.\n\nA closed list, because a client files games under these and has to name each one in front of a player: left to free text, one kind of game becomes three spellings of it with a game each. The list only grows, since a published manifest has to stay valid. Whoever reads one may be older than the game, and ignores a word it does not know.\n\n`local-multiplayer` and `online-multiplayer` say how a game is played rather than what it is, and only count where the manifest bears them out: see `declaredGenres`.",
|
|
209
|
+
"markdownDescription": "What kind of game this is, as one word from a list every game shares.\n\nA closed list, because a client files games under these and has to name each\none in front of a player: left to free text, one kind of game becomes three\nspellings of it with a game each. The list only grows, since a published\nmanifest has to stay valid. Whoever reads one may be older than the game, and\nignores a word it does not know.\n\n`local-multiplayer` and `online-multiplayer` say how a game is played rather\nthan what it is, and only count where the manifest bears them out: see\n`declaredGenres`."
|
|
160
210
|
},
|
|
161
211
|
"Orientation": {
|
|
162
212
|
"type": "string",
|
|
@@ -178,7 +228,7 @@
|
|
|
178
228
|
"required": [
|
|
179
229
|
"insets"
|
|
180
230
|
],
|
|
181
|
-
"additionalProperties":
|
|
231
|
+
"additionalProperties": false,
|
|
182
232
|
"description": "What a game declares about layout. Absent means every default.",
|
|
183
233
|
"markdownDescription": "What a game declares about layout. Absent means every default."
|
|
184
234
|
},
|
|
@@ -215,7 +265,7 @@
|
|
|
215
265
|
"required": [
|
|
216
266
|
"events"
|
|
217
267
|
],
|
|
218
|
-
"additionalProperties":
|
|
268
|
+
"additionalProperties": false,
|
|
219
269
|
"description": "The custom events a game sends, and what each one carries.\n\n**The first key here whose reader is not the host.** `layout.insets` is declared because a frame has to be sized before there is a game to ask; nothing about analytics has to be answered before the bundle runs. What earns this its place is cardinality: names and columns invented at runtime make a pipe nobody can build a table for, and the party that has to build that table — the publish CLI, the server, a creator dashboard — is exactly the party that already reads the manifest and never talks to a game.\n\n**Every prop is optional and nothing is required**, which is what keeps the declaration cheap enough to be worth asking for: forgetting one costs a column rather than the event. The additive rule carries over unchanged — add events, add props, never retype one — and that is the whole of what a per-event table needs to survive the game's next version.",
|
|
220
270
|
"markdownDescription": "The custom events a game sends, and what each one carries.\n\n**The first key here whose reader is not the host.** `layout.insets` is\ndeclared because a frame has to be sized before there is a game to ask;\nnothing about analytics has to be answered before the bundle runs. What earns\nthis its place is cardinality: names and columns invented at runtime make a\npipe nobody can build a table for, and the party that has to build that table\n— the publish CLI, the server, a creator dashboard — is exactly the party that\nalready reads the manifest and never talks to a game.\n\n**Every prop is optional and nothing is required**, which is what keeps the\ndeclaration cheap enough to be worth asking for: forgetting one costs a column\nrather than the event. The additive rule carries over unchanged — add events,\nadd props, never retype one — and that is the whole of what a per-event table\nneeds to survive the game's next version."
|
|
221
271
|
},
|
|
@@ -257,7 +307,7 @@
|
|
|
257
307
|
"required": [
|
|
258
308
|
"bytes"
|
|
259
309
|
],
|
|
260
|
-
"additionalProperties":
|
|
310
|
+
"additionalProperties": false,
|
|
261
311
|
"description": "What a game expects to keep, in bytes.\n\n**The manifest's own test is whether the host must know before the bundle runs**, and here it must: a host that prefetches a save from a server has to know which games have one before there is a game to ask, and a publish pipeline that provisions storage has to know before a player exists.\n\nThe budget the game expects, not a grant. A host answers with its own cap at the handshake, and a game with no key here may still call the API — it just gets nothing prefetched, which is the honest reading of not having said so.",
|
|
262
312
|
"markdownDescription": "What a game expects to keep, in bytes.\n\n**The manifest's own test is whether the host must know before the bundle\nruns**, and here it must: a host that prefetches a save from a server has to\nknow which games have one before there is a game to ask, and a publish\npipeline that provisions storage has to know before a player exists.\n\nThe budget the game expects, not a grant. A host answers with its own cap at\nthe handshake, and a game with no key here may still call the API — it just\ngets nothing prefetched, which is the honest reading of not having said so."
|
|
263
313
|
},
|
|
@@ -283,7 +333,7 @@
|
|
|
283
333
|
"required": [
|
|
284
334
|
"boards"
|
|
285
335
|
],
|
|
286
|
-
"additionalProperties":
|
|
336
|
+
"additionalProperties": false,
|
|
287
337
|
"description": "The boards this game keeps a score on.\n\n**A map from the first version, even for a game that wants one board.** The board id travels in `score.submit`, which is frozen game-side code, and a single-board key would have to *change meaning* to grow a second one — the one thing the additive rule forbids. A map costs a game with one board the few characters of naming it, and costs a game with four nothing at all.\n\n**Read by the host and by the server**, which is what earns it a key rather than a runtime call: a client draws a game's board in a sheet over a feed without ever having run that game, and the server has to know how a board sorts before it can answer a query about one.",
|
|
288
338
|
"markdownDescription": "The boards this game keeps a score on.\n\n**A map from the first version, even for a game that wants one board.** The\nboard id travels in `score.submit`, which is frozen game-side code, and a\nsingle-board key would have to *change meaning* to grow a second one — the\none thing the additive rule forbids. A map costs a game with one board the\nfew characters of naming it, and costs a game with four nothing at all.\n\n**Read by the host and by the server**, which is what earns it a key rather\nthan a runtime call: a client draws a game's board in a sheet over a feed\nwithout ever having run that game, and the server has to know how a board\nsorts before it can answer a query about one."
|
|
289
339
|
},
|
|
@@ -311,7 +361,7 @@
|
|
|
311
361
|
"title",
|
|
312
362
|
"direction"
|
|
313
363
|
],
|
|
314
|
-
"additionalProperties":
|
|
364
|
+
"additionalProperties": false
|
|
315
365
|
},
|
|
316
366
|
"ScoreDirection": {
|
|
317
367
|
"type": "string",
|
|
@@ -354,7 +404,7 @@
|
|
|
354
404
|
"required": [
|
|
355
405
|
"list"
|
|
356
406
|
],
|
|
357
|
-
"additionalProperties":
|
|
407
|
+
"additionalProperties": false,
|
|
358
408
|
"description": "The achievements this game keeps for a player.\n\n**A map, for the reason `score.boards` is one**: the id travels in `achievement.unlock`, which is frozen game-side code, so the shape has to hold a second entry without anything changing meaning.\n\n**Read by the host, by the client and by the server, and by none of them from the game.** The site draws this panel for a game sitting in the feed that has never been run; the server refuses an id no publisher declared. That is what earns a manifest key rather than a runtime call — the same test `score` passed.",
|
|
359
409
|
"markdownDescription": "The achievements this game keeps for a player.\n\n**A map, for the reason `score.boards` is one**: the id travels in\n`achievement.unlock`, which is frozen game-side code, so the shape has to\nhold a second entry without anything changing meaning.\n\n**Read by the host, by the client and by the server, and by none of them from\nthe game.** The site draws this panel for a game sitting in the feed that has\nnever been run; the server refuses an id no publisher declared. That is what\nearns a manifest key rather than a runtime call — the same test `score`\npassed."
|
|
360
410
|
},
|
|
@@ -399,7 +449,7 @@
|
|
|
399
449
|
"required": [
|
|
400
450
|
"title"
|
|
401
451
|
],
|
|
402
|
-
"additionalProperties":
|
|
452
|
+
"additionalProperties": false,
|
|
403
453
|
"description": "One thing a player can be shown to have done.\n\n**Two kinds, told apart by one key.** Without `progress` this is a flag: it happens once, and saying so twice is the same fact twice. With `progress` it is measured — a count, a distance, a best time — reported as the game goes and unlocked when the value reaches the target.\n\n**`secret` withholds the words, never the row.** A secret achievement is listed while it is locked, so a player can see there is one left, with its title and detail held back until it is theirs. Hiding the row itself would make the list's length change under the player, which reads as a bug rather than as a surprise. It is a courtesy of the panel and not a secret kept from the machine: the manifest ships beside the bundle and is public, so anybody who wants the words already has them.",
|
|
404
454
|
"markdownDescription": "One thing a player can be shown to have done.\n\n**Two kinds, told apart by one key.** Without `progress` this is a flag: it\nhappens once, and saying so twice is the same fact twice. With `progress` it\nis measured — a count, a distance, a best time — reported as the game goes\nand unlocked when the value reaches the target.\n\n**`secret` withholds the words, never the row.** A secret achievement is\nlisted while it is locked, so a player can see there is one left, with its\ntitle and detail held back until it is theirs. Hiding the row itself would\nmake the list's length change under the player, which reads as a bug rather\nthan as a surprise. It is a courtesy of the panel and not a secret kept from\nthe machine: the manifest ships beside the bundle and is public, so anybody\nwho wants the words already has them."
|
|
405
455
|
},
|
|
@@ -436,7 +486,7 @@
|
|
|
436
486
|
"required": [
|
|
437
487
|
"target"
|
|
438
488
|
],
|
|
439
|
-
"additionalProperties":
|
|
489
|
+
"additionalProperties": false,
|
|
440
490
|
"description": "What makes an achievement measured rather than flagged.\n\n**`direction` is the one thing that cannot be guessed**, exactly as it cannot be guessed for a board. `desc` — the default, and what most achievements are — counts up to the target: *break 100 blocks*. `asc` counts down to it:\n*finish under two minutes*, where the value kept is the player's best time and a smaller one beats a larger. A panel that assumed bigger-is-better would draw a stopwatch achievement as though every slow run were progress.\n\n**`format` is how to draw the number**, for the reason a board needs one: whoever renders this has never run the game and cannot ask it what `112400` means.\n\nBoth are `ScoreDirection` and `ScoreFormat` rather than types of their own. \"Which way is better\" and \"how is this number written\" are the same two questions wherever they are asked, and a second spelling of each is two vocabularies to keep in step and one bug when they drift.",
|
|
441
491
|
"markdownDescription": "What makes an achievement measured rather than flagged.\n\n**`direction` is the one thing that cannot be guessed**, exactly as it cannot\nbe guessed for a board. `desc` — the default, and what most achievements are\n— counts up to the target: *break 100 blocks*. `asc` counts down to it:\n*finish under two minutes*, where the value kept is the player's best time\nand a smaller one beats a larger. A panel that assumed bigger-is-better would\ndraw a stopwatch achievement as though every slow run were progress.\n\n**`format` is how to draw the number**, for the reason a board needs one:\nwhoever renders this has never run the game and cannot ask it what `112400`\nmeans.\n\nBoth are `ScoreDirection` and `ScoreFormat` rather than types of their own.\n\"Which way is better\" and \"how is this number written\" are the same two\nquestions wherever they are asked, and a second spelling of each is two\nvocabularies to keep in step and one bug when they drift."
|
|
442
492
|
},
|
|
@@ -471,7 +521,7 @@
|
|
|
471
521
|
"at",
|
|
472
522
|
"title"
|
|
473
523
|
],
|
|
474
|
-
"additionalProperties":
|
|
524
|
+
"additionalProperties": false,
|
|
475
525
|
"description": "One named step on the way to a measured achievement's target.\n\n**A tier is a label, never a second achievement.** The achievement is unlocked at its `target` and nowhere else; a tier is what a panel calls the player between here and there — *Silver, 50 of 100*. Modelling each tier as its own id would be three rows in every board that draws them and three unlocks a game has to remember to send, where this is one number reported once and read three ways.",
|
|
476
526
|
"markdownDescription": "One named step on the way to a measured achievement's target.\n\n**A tier is a label, never a second achievement.** The achievement is\nunlocked at its `target` and nowhere else; a tier is what a panel calls the\nplayer between here and there — *Silver, 50 of 100*. Modelling each tier as\nits own id would be three rows in every board that draws them and three\nunlocks a game has to remember to send, where this is one number reported\nonce and read three ways."
|
|
477
527
|
},
|
|
@@ -515,9 +565,14 @@
|
|
|
515
565
|
"maxLength": 48,
|
|
516
566
|
"errorMessage": "must be a layout name of lowercase letters, digits, dots, hyphens and underscores"
|
|
517
567
|
}
|
|
568
|
+
},
|
|
569
|
+
"player": {
|
|
570
|
+
"$ref": "#/definitions/PlayerInputDeclaration",
|
|
571
|
+
"description": "What each local player does, beside `actions`, which are everybody's. See `PlayerInputDeclaration`.",
|
|
572
|
+
"markdownDescription": "What each local player does, beside `actions`, which are everybody's. See\n`PlayerInputDeclaration`."
|
|
518
573
|
}
|
|
519
574
|
},
|
|
520
|
-
"additionalProperties":
|
|
575
|
+
"additionalProperties": false,
|
|
521
576
|
"description": "What a game says about how it is played.\n\n**It passes the manifest's test twice.** The host must know before the bundle runs — whether on-screen controls apply at all, and what a remapping screen offers, are settled before there is a game to ask — and it is read by parties that never run the game: a feed deciding whether to show a keyboard-only game to somebody holding a phone, and a remapping screen opened from outside a run.\n\nAction, control and layout ids follow the grammar the wire does, and a test keeps the two in step.",
|
|
522
577
|
"markdownDescription": "What a game says about how it is played.\n\n**It passes the manifest's test twice.** The host must know before the bundle\nruns — whether on-screen controls apply at all, and what a remapping screen\noffers, are settled before there is a game to ask — and it is read by parties\nthat never run the game: a feed deciding whether to show a keyboard-only game\nto somebody holding a phone, and a remapping screen opened from outside a run.\n\nAction, control and layout ids follow the grammar the wire does, and a test\nkeeps the two in step."
|
|
523
578
|
},
|
|
@@ -570,7 +625,7 @@
|
|
|
570
625
|
"required": [
|
|
571
626
|
"bind"
|
|
572
627
|
],
|
|
573
|
-
"additionalProperties":
|
|
628
|
+
"additionalProperties": false,
|
|
574
629
|
"description": "One thing a player does, named by the game and bound to whatever produces it.\n\n**An action is a button and never an axis.** A key, a face button on a pad, a pointer button and an on-screen button all press the same action, because from the game's side they are the same event. A thumbstick is not one of those and is read through its own API — folding the two together would put a range on every binding that mostly does not have one, and would leave a game unable to say whether it meant the press or the lean.\n\n**An object rather than a bare list of bindings**, from the first version, because two strings are wanted the moment anybody draws this: a `title` for the remapping screen, which shows actions to a player and cannot show `move.left`, and a `label` for the face of an on-screen button. Growing a list into an object later would change what an action's value *is*, which is the one move the additive rule forbids. Only the object is accepted, even for an action that needs neither string: one way to write an action is one way to read it.",
|
|
575
630
|
"markdownDescription": "One thing a player does, named by the game and bound to whatever produces it.\n\n**An action is a button and never an axis.** A key, a face button on a pad, a\npointer button and an on-screen button all press the same action, because\nfrom the game's side they are the same event. A thumbstick is not one of\nthose and is read through its own API — folding the two together would put a\nrange on every binding that mostly does not have one, and would leave a game\nunable to say whether it meant the press or the lean.\n\n**An object rather than a bare list of bindings**, from the first version,\nbecause two strings are wanted the moment anybody draws this: a `title` for\nthe remapping screen, which shows actions to a player and cannot show\n`move.left`, and a `label` for the face of an on-screen button. Growing a\nlist into an object later would change what an action's value *is*, which is\nthe one move the additive rule forbids. Only the object is accepted, even for\nan action that needs neither string: one way to write an action is one way to\nread it."
|
|
576
631
|
},
|
|
@@ -604,9 +659,9 @@
|
|
|
604
659
|
"$ref": "#/definitions/ControlZoneSlots"
|
|
605
660
|
}
|
|
606
661
|
},
|
|
607
|
-
"additionalProperties":
|
|
608
|
-
"description": "One named arrangement of on-screen controls.\n\nA game may declare several and switch between them — a menu wants different controls from play, and a driving section different ones again — which is why they are named rather than there being one. A zone nobody knows is a layout from a newer
|
|
609
|
-
"markdownDescription": "One named arrangement of on-screen controls.\n\nA game may declare several and switch between them — a menu wants different\ncontrols from play, and a driving section different ones again — which is why\nthey are named rather than there being one. A zone nobody knows is a layout\nfrom a newer
|
|
662
|
+
"additionalProperties": false,
|
|
663
|
+
"description": "One named arrangement of on-screen controls.\n\nA game may declare several and switch between them — a menu wants different controls from play, and a driving section different ones again — which is why they are named rather than there being one. A zone nobody knows is a layout from a newer minor, which a host reads leniently, so whoever draws it simply has nowhere to put it.",
|
|
664
|
+
"markdownDescription": "One named arrangement of on-screen controls.\n\nA game may declare several and switch between them — a menu wants different\ncontrols from play, and a driving section different ones again — which is why\nthey are named rather than there being one. A zone nobody knows is a layout\nfrom a newer minor, which a host reads leniently, so whoever draws it simply\nhas nowhere to put it."
|
|
610
665
|
},
|
|
611
666
|
"ControlZoneSlots": {
|
|
612
667
|
"type": "object",
|
|
@@ -666,12 +721,19 @@
|
|
|
666
721
|
"$ref": "#/definitions/ControlAxis",
|
|
667
722
|
"description": "`thumbstick` only. See `ControlAxis`. Absent, the stick moves both ways.",
|
|
668
723
|
"markdownDescription": "`thumbstick` only. See `ControlAxis`. Absent, the stick moves both ways."
|
|
724
|
+
},
|
|
725
|
+
"scale": {
|
|
726
|
+
"type": "number",
|
|
727
|
+
"description": "How big this control is next to the layout's others: `1.25` for a jump button a thumb lands on without looking, `0.8` for one pressed now and then. Absent, 1.\n\nA size in the layout rather than in the game's stylesheet, because whether the layout fits on a small screen is worked out from it before the controls are drawn. A game's stylesheet changes how they look, never how much of the screen they answer in.",
|
|
728
|
+
"markdownDescription": "How big this control is next to the layout's others: `1.25` for a jump\nbutton a thumb lands on without looking, `0.8` for one pressed now and then.\nAbsent, 1.\n\nA size in the layout rather than in the game's stylesheet, because whether\nthe layout fits on a small screen is worked out from it before the\ncontrols are drawn. A game's stylesheet changes how they look, never how\nmuch of the screen they answer in.",
|
|
729
|
+
"minimum": 0.75,
|
|
730
|
+
"maximum": 1.5
|
|
669
731
|
}
|
|
670
732
|
},
|
|
671
733
|
"required": [
|
|
672
734
|
"type"
|
|
673
735
|
],
|
|
674
|
-
"additionalProperties":
|
|
736
|
+
"additionalProperties": false,
|
|
675
737
|
"description": "One on-screen control: a group of buttons, or a thumbstick.\n\n**A button slot names an action; a stick slot names a stick.** The asymmetry is the hardware's. On-screen buttons have no fixed count or position — three across, five down, four in a diamond — so there is no standard control to unify them with, and the action is the only stable name they have. Thumbsticks come in twos and everybody agrees which is which, so a virtual one says which it stands for and a game reads the physical stick and this one through one id.\n\nThere is no d-pad here. A d-pad is four buttons wherever it exists — four entries on the wire from a real pad, four keys on a keyboard — so a layout that wants one declares four buttons and binds them like anything else.",
|
|
676
738
|
"markdownDescription": "One on-screen control: a group of buttons, or a thumbstick.\n\n**A button slot names an action; a stick slot names a stick.** The asymmetry\nis the hardware's. On-screen buttons have no fixed count or position — three\nacross, five down, four in a diamond — so there is no standard control to\nunify them with, and the action is the only stable name they have. Thumbsticks\ncome in twos and everybody agrees which is which, so a virtual one says which\nit stands for and a game reads the physical stick and this one through one id.\n\nThere is no d-pad here. A d-pad is four buttons wherever it exists — four\nentries on the wire from a real pad, four keys on a keyboard — so a layout\nthat wants one declares four buttons and binds them like anything else.",
|
|
677
739
|
"if": {
|
|
@@ -736,6 +798,145 @@
|
|
|
736
798
|
"description": "The one direction an on-screen thumbstick moves in: `x` across, `y` up and down.\n\nFor a game that steers along one line — a runner sliding left and right, a paddle — where a stick that also leans up and down is a thumb wandering off the line it means. The drawn stick becomes a track along that axis and always reports the other one as 0. **Only the drawn stick:** a real pad's stick still reports both, so a game reads the axis it wants from either.\n\nWhoever draws the controls may be older than the game, and draw a stick that moves both ways. The SDK zeroes the other axis whatever the renderer reports, so the game hears one axis either way.",
|
|
737
799
|
"markdownDescription": "The one direction an on-screen thumbstick moves in: `x` across, `y` up and\ndown.\n\nFor a game that steers along one line — a runner sliding left and right, a\npaddle — where a stick that also leans up and down is a thumb wandering off\nthe line it means. The drawn stick becomes a track along that axis and always\nreports the other one as 0. **Only the drawn stick:** a real pad's stick still\nreports both, so a game reads the axis it wants from either.\n\nWhoever draws the controls may be older than the game, and draw a stick that\nmoves both ways. The SDK zeroes the other axis whatever the renderer reports,\nso the game hears one axis either way."
|
|
738
800
|
},
|
|
801
|
+
"PlayerInputDeclaration": {
|
|
802
|
+
"type": "object",
|
|
803
|
+
"properties": {
|
|
804
|
+
"actions": {
|
|
805
|
+
"type": "object",
|
|
806
|
+
"additionalProperties": {
|
|
807
|
+
"$ref": "#/definitions/PlayerInputAction"
|
|
808
|
+
},
|
|
809
|
+
"description": "The actions each player has, by id: never an id `actions` declares too.",
|
|
810
|
+
"markdownDescription": "The actions each player has, by id: never an id `actions` declares too.",
|
|
811
|
+
"maxProperties": 32,
|
|
812
|
+
"propertyNames": {
|
|
813
|
+
"pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*$",
|
|
814
|
+
"maxLength": 48,
|
|
815
|
+
"errorMessage": "must be lowercase letters, digits, dots, hyphens and underscores"
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
"keyboard": {
|
|
819
|
+
"type": "array",
|
|
820
|
+
"items": {
|
|
821
|
+
"$ref": "#/definitions/KeyboardSet"
|
|
822
|
+
},
|
|
823
|
+
"description": "The parts of the keyboard players play with, one player each, in the order they are offered. A player alone hears every one of them. Left out, the keyboard presses no player's action.",
|
|
824
|
+
"markdownDescription": "The parts of the keyboard players play with, one player each, in the\norder they are offered. A player alone hears every one of them. Left out,\nthe keyboard presses no player's action.",
|
|
825
|
+
"minItems": 1,
|
|
826
|
+
"maxItems": 4
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
"required": [
|
|
830
|
+
"actions"
|
|
831
|
+
],
|
|
832
|
+
"additionalProperties": false,
|
|
833
|
+
"description": "What each local player does, and how they share the keyboard.\n\n**One set of actions for every player.** Each local player has their own copy, pressed by the pad they hold, the part of the keyboard they play with, or the on-screen controls. A pad's buttons are the same for every pad, so any number of pads just works; the keyboard is split into sets, one player each, so the number of sets is how many can share it.",
|
|
834
|
+
"markdownDescription": "What each local player does, and how they share the keyboard.\n\n**One set of actions for every player.** Each local player has their own\ncopy, pressed by the pad they hold, the part of the keyboard they play with,\nor the on-screen controls. A pad's buttons are the same for every pad, so any\nnumber of pads just works; the keyboard is split into sets, one player each,\nso the number of sets is how many can share it."
|
|
835
|
+
},
|
|
836
|
+
"PlayerInputAction": {
|
|
837
|
+
"type": "object",
|
|
838
|
+
"properties": {
|
|
839
|
+
"bind": {
|
|
840
|
+
"type": "array",
|
|
841
|
+
"items": {
|
|
842
|
+
"$ref": "#/definitions/PlayerBinding"
|
|
843
|
+
},
|
|
844
|
+
"description": "What presses it on a pad, the same for every pad, and on the screen. Leave it out for an action pressed only on the keyboard.",
|
|
845
|
+
"markdownDescription": "What presses it on a pad, the same for every pad, and on the screen.\nLeave it out for an action pressed only on the keyboard.",
|
|
846
|
+
"minItems": 1,
|
|
847
|
+
"maxItems": 8
|
|
848
|
+
},
|
|
849
|
+
"title": {
|
|
850
|
+
"type": "string",
|
|
851
|
+
"description": "What to call it in front of a player, in a remapping screen.",
|
|
852
|
+
"markdownDescription": "What to call it in front of a player, in a remapping screen.",
|
|
853
|
+
"pattern": "\\S",
|
|
854
|
+
"maxLength": 40,
|
|
855
|
+
"errorMessage": "must not be blank"
|
|
856
|
+
},
|
|
857
|
+
"label": {
|
|
858
|
+
"type": "string",
|
|
859
|
+
"description": "What to draw on an on-screen button: a glyph, or a letter.",
|
|
860
|
+
"markdownDescription": "What to draw on an on-screen button: a glyph, or a letter.",
|
|
861
|
+
"pattern": "\\S",
|
|
862
|
+
"maxLength": 8,
|
|
863
|
+
"errorMessage": "must not be blank"
|
|
864
|
+
},
|
|
865
|
+
"join": {
|
|
866
|
+
"type": "boolean",
|
|
867
|
+
"description": "Whether pressing it on a pad or a part of the keyboard nobody plays with makes a new player, while the game is taking players. Left out on every action, any of them does.",
|
|
868
|
+
"markdownDescription": "Whether pressing it on a pad or a part of the keyboard nobody plays with\nmakes a new player, while the game is taking players. Left out on every\naction, any of them does."
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
"additionalProperties": false,
|
|
872
|
+
"description": "One thing each player does: the same action for every local player, pressed by their own pad, their own part of the keyboard, or the on-screen controls.",
|
|
873
|
+
"markdownDescription": "One thing each player does: the same action for every local player, pressed\nby their own pad, their own part of the keyboard, or the on-screen controls."
|
|
874
|
+
},
|
|
875
|
+
"PlayerBinding": {
|
|
876
|
+
"type": "string",
|
|
877
|
+
"description": "What presses a player's action on a pad or on the screen: a pad button, or an on-screen control. Keys and mouse buttons are a keyboard set's, in `PlayerInputDeclaration.keyboard`.",
|
|
878
|
+
"markdownDescription": "What presses a player's action on a pad or on the screen: a pad button, or\nan on-screen control. Keys and mouse buttons are a keyboard set's, in\n`PlayerInputDeclaration.keyboard`.",
|
|
879
|
+
"pattern": "^(?:controls\\.(?=.{1,48}$)[a-z0-9]+(?:[_-][a-z0-9]+)*|pad\\.(?:north|south|east|west|start|select)|pad\\.(?:left|right)\\.(?:north|south|east|west|bumper|trigger))$",
|
|
880
|
+
"errorMessage": "must be a pad button or an on-screen control: a player's keys and mouse buttons go in input.player.keyboard"
|
|
881
|
+
},
|
|
882
|
+
"KeyboardSet": {
|
|
883
|
+
"type": "object",
|
|
884
|
+
"properties": {
|
|
885
|
+
"title": {
|
|
886
|
+
"type": "string",
|
|
887
|
+
"description": "What the players call it: *Left keys*, *Arrows*.",
|
|
888
|
+
"markdownDescription": "What the players call it: *Left keys*, *Arrows*.",
|
|
889
|
+
"pattern": "\\S",
|
|
890
|
+
"maxLength": 40,
|
|
891
|
+
"errorMessage": "must not be blank"
|
|
892
|
+
},
|
|
893
|
+
"bind": {
|
|
894
|
+
"type": "object",
|
|
895
|
+
"additionalProperties": {
|
|
896
|
+
"type": "array",
|
|
897
|
+
"items": {
|
|
898
|
+
"$ref": "#/definitions/KeyboardBinding"
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
"description": "The keys and mouse buttons of each player action, by its id.",
|
|
902
|
+
"markdownDescription": "The keys and mouse buttons of each player action, by its id.",
|
|
903
|
+
"maxProperties": 64,
|
|
904
|
+
"propertyNames": {
|
|
905
|
+
"pattern": "^[a-z0-9]+(?:[._-][a-z0-9]+)*$",
|
|
906
|
+
"maxLength": 48,
|
|
907
|
+
"errorMessage": "must be a player action id"
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
"required": [
|
|
912
|
+
"bind"
|
|
913
|
+
],
|
|
914
|
+
"additionalProperties": false,
|
|
915
|
+
"description": "One part of the keyboard one player plays with: the keys and mouse buttons that press each of the player actions there.",
|
|
916
|
+
"markdownDescription": "One part of the keyboard one player plays with: the keys and mouse buttons\nthat press each of the player actions there."
|
|
917
|
+
},
|
|
918
|
+
"KeyboardBinding": {
|
|
919
|
+
"type": "string",
|
|
920
|
+
"description": "What presses a player's action on their part of the keyboard: a key, or a mouse button.",
|
|
921
|
+
"markdownDescription": "What presses a player's action on their part of the keyboard: a key, or a\nmouse button.",
|
|
922
|
+
"pattern": "^(?:key\\.(?!Escape$)(?=.{1,48}$)[A-Za-z][A-Za-z0-9]*|mouse\\.(?:left|middle|right))$",
|
|
923
|
+
"errorMessage": "must be a key or a mouse button (and Escape is the host's key)"
|
|
924
|
+
},
|
|
925
|
+
"PlayersDeclaration": {
|
|
926
|
+
"type": "object",
|
|
927
|
+
"properties": {
|
|
928
|
+
"local": {
|
|
929
|
+
"type": "integer",
|
|
930
|
+
"description": "The most players on one device, each with their own copy of `input.player`'s actions. 1 when left out.",
|
|
931
|
+
"markdownDescription": "The most players on one device, each with their own copy of\n`input.player`'s actions. 1 when left out.",
|
|
932
|
+
"minimum": 1,
|
|
933
|
+
"maximum": 8
|
|
934
|
+
}
|
|
935
|
+
},
|
|
936
|
+
"additionalProperties": false,
|
|
937
|
+
"description": "How many people play a game on one device.\n\nRead by hosts before the game runs: a listing says *1–4 on one device*, and the input panel offers a row for each player.",
|
|
938
|
+
"markdownDescription": "How many people play a game on one device.\n\nRead by hosts before the game runs: a listing says *1–4 on one device*, and\nthe input panel offers a row for each player."
|
|
939
|
+
},
|
|
739
940
|
"AudioDeclaration": {
|
|
740
941
|
"type": "object",
|
|
741
942
|
"properties": {
|
|
@@ -757,7 +958,7 @@
|
|
|
757
958
|
}
|
|
758
959
|
}
|
|
759
960
|
},
|
|
760
|
-
"additionalProperties":
|
|
961
|
+
"additionalProperties": false,
|
|
761
962
|
"description": "What this game makes noise with, so a host can offer a fader for each.\n\n**A map from the first version, even for a game with one channel.** The channel id travels in `audio.set`, which is frozen game-side code, and a single-channel key would have to *change meaning* to grow a second one — the one move the additive rule forbids.\n\n**The manifest's test is whether the host must know before the bundle runs**, and here it must twice over. The panel is drawn where the game's title is, which a host draws for games in a feed that nobody has opened; and the opening volumes ride in the handshake, so the host has to know which channels it is stating a volume for before there is a game to ask.\n\n**The key is required, and `null` is how a silent game says so.** Leaving it out would read the same as forgetting it, and a game that makes noise but never said so is one whose player cannot turn it down. An object with no channels is a game with sound and no tracks worth a fader of their own: it gets a master fader and a mute button and no more. What is never optional is obeying what the host then says: see `AudioState`.",
|
|
762
963
|
"markdownDescription": "What this game makes noise with, so a host can offer a fader for each.\n\n**A map from the first version, even for a game with one channel.** The\nchannel id travels in `audio.set`, which is frozen game-side code, and a\nsingle-channel key would have to *change meaning* to grow a second one — the\none move the additive rule forbids.\n\n**The manifest's test is whether the host must know before the bundle runs**,\nand here it must twice over. The panel is drawn where the game's title is,\nwhich a host draws for games in a feed that nobody has opened; and the\nopening volumes ride in the handshake, so the host has to know which channels\nit is stating a volume for before there is a game to ask.\n\n**The key is required, and `null` is how a silent game says so.** Leaving it\nout would read the same as forgetting it, and a game that makes noise but\nnever said so is one whose player cannot turn it down. An object with no\nchannels is a game with sound and no tracks worth a fader of their own: it\ngets a master fader and a mute button and no more. What is never optional is\nobeying what the host then says: see `AudioState`."
|
|
763
964
|
},
|
|
@@ -776,9 +977,28 @@
|
|
|
776
977
|
"required": [
|
|
777
978
|
"title"
|
|
778
979
|
],
|
|
779
|
-
"additionalProperties":
|
|
980
|
+
"additionalProperties": false,
|
|
780
981
|
"description": "One thing a game makes noise with, as the player is offered it.\n\nThe title is the whole of it, and required: this exists to be a label on a fader in a host's own panel, and a fader labelled `sfx2` is a control nobody can use. What a channel *is* — a bus, a set of samples, one oscillator — is the game's business and is never described here.",
|
|
781
982
|
"markdownDescription": "One thing a game makes noise with, as the player is offered it.\n\nThe title is the whole of it, and required: this exists to be a label on a\nfader in a host's own panel, and a fader labelled `sfx2` is a control nobody\ncan use. What a channel *is* — a bus, a set of samples, one oscillator — is\nthe game's business and is never described here."
|
|
983
|
+
},
|
|
984
|
+
"PreviewsDeclaration": {
|
|
985
|
+
"type": "object",
|
|
986
|
+
"properties": {
|
|
987
|
+
"network": {
|
|
988
|
+
"$ref": "#/definitions/PreviewRevision",
|
|
989
|
+
"description": "That this game plays over the network, in matches, at this revision of `@hopple/sdk-preview-network`: `sdk.match`.\n\nThe permission a reviewer grants, and what lets a host offer *Play with friends* before the game has loaded. Nothing else about networking is declared: the way a match is played, and its size, are chosen by the game when it makes one. Absent, a game is never told about a match, and never reaches one.",
|
|
990
|
+
"markdownDescription": "That this game plays over the network, in matches, at this revision of\n`@hopple/sdk-preview-network`: `sdk.match`.\n\nThe permission a reviewer grants, and what lets a host offer *Play with\nfriends* before the game has loaded. Nothing else about networking is\ndeclared: the way a match is played, and its size, are chosen by the game\nwhen it makes one. Absent, a game is never told about a match, and never\nreaches one."
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
"additionalProperties": false,
|
|
994
|
+
"description": "The previews a game is written against, each at the revision it needs.\n\nA preview is a part of the SDK that is not stable yet: its own `0.x` package, versioned apart from `api`, and served only to the games that declare it, at the revision they declare. A change to a preview reaches the games written against it and nobody else.",
|
|
995
|
+
"markdownDescription": "The previews a game is written against, each at the revision it needs.\n\nA preview is a part of the SDK that is not stable yet: its own `0.x`\npackage, versioned apart from `api`, and served only to the games that\ndeclare it, at the revision they declare. A change to a preview reaches the\ngames written against it and nobody else."
|
|
996
|
+
},
|
|
997
|
+
"PreviewRevision": {
|
|
998
|
+
"type": "integer",
|
|
999
|
+
"description": "A preview's revision: a whole number from 1, the `R` of its package's `0.R.p`. A breaking change to the preview is a new revision.",
|
|
1000
|
+
"markdownDescription": "A preview's revision: a whole number from 1, the `R` of its package's\n`0.R.p`. A breaking change to the preview is a new revision.",
|
|
1001
|
+
"minimum": 1
|
|
782
1002
|
}
|
|
783
1003
|
}
|
|
784
1004
|
}
|