games.atmosphere.define
Schema Diff
+92 -93
Compatibility Analysis
Breaking Changes Detected
1 breaking change, 3 non-breaking changes.
Breaking Changes (1)
- ConstraintTightened ConstraintTightened { vertex_id: "games.atmosphere.define#recordUse.role", sort: "knownValues", old_value: "[\"character\",\"perk\",\"cosmetic\",\"bonus\",\"interop\",\"stats\",\"activity\",\"leaderboard\",\"save\",\"replay\",\"session\",\"profile\",\"inventory\",\"grant\",\"validation\",\"label\",\"other\"]", new_value: "[\"character\",\"perk\",\"cosmetic\",\"interop\",\"stats\",\"activity\",\"leaderboard\",\"profile\",\"session\",\"inventory\",\"other\"]" }
Non-Breaking Changes (3)
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#rule.verify" }
- AddedEdge AddedEdge { src: "games.atmosphere.define#rule", tgt: "games.atmosphere.define#rule.verify", kind: "prop", name: Some("verify") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#rule.verify", tgt: "games.atmosphere.define#rule", kind: "ref", name: None }
Migration Guidance
Added Elements
AddedVertex { vertex_id: "games.atmosphere.define#rule.verify" }
Constraint Changes
- ConstraintTightened ConstraintTightened { vertex_id: "games.atmosphere.define#recordUse.role", sort: "knownValues", old_value: "[\"character\",\"perk\",\"cosmetic\",\"bonus\",\"interop\",\"stats\",\"activity\",\"leaderboard\",\"save\",\"replay\",\"session\",\"profile\",\"inventory\",\"grant\",\"validation\",\"label\",\"other\"]", new_value: "[\"character\",\"perk\",\"cosmetic\",\"interop\",\"stats\",\"activity\",\"leaderboard\",\"profile\",\"session\",\"inventory\",\"other\"]" }
Additional Notes
- Non-breaking: AddedEdge { src: "games.atmosphere.define#rule", tgt: "games.atmosphere.define#rule.verify", kind: "prop", name: Some("verify") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#rule.verify", tgt: "games.atmosphere.define#rule", kind: "ref", name: None }
1
1
{
2
2
"id": "games.atmosphere.define",
3
3
"defs": {
4
4
"all": {
5
5
"type": "object",
6
6
"required": [
7
7
"check",
8
8
"rules"
9
9
],
10
10
"properties": {
11
11
"check": {
12
12
"type": "string",
13
13
"const": "all"
14
14
},
15
15
"label": {
16
16
"type": "string",
17
17
"maxLength": 120
18
18
},
19
19
"rules": {
20
20
"type": "array",
21
21
"items": {
22
22
"ref": "#rule",
23
23
"type": "ref"
24
24
},
25
25
"maxLength": 16,
26
26
"minLength": 1
27
27
},
28
28
"acquire": {
29
29
"type": "string",
30
30
"format": "uri"
31
31
}
32
32
},
33
-
"description": "Logical AND. Passes only when every sub-rule passes."
33
+
"description": "Passes only when every rule passes."
34
34
},
35
35
"any": {
36
36
"type": "object",
37
37
"required": [
38
38
"check",
39
39
"rules"
40
40
],
41
41
"properties": {
42
42
"check": {
43
43
"type": "string",
44
44
"const": "any"
45
45
},
46
46
"label": {
47
47
"type": "string",
48
48
"maxLength": 120
49
49
},
50
50
"rules": {
51
51
"type": "array",
52
52
"items": {
53
53
"ref": "#rule",
54
54
"type": "ref"
55
55
},
56
56
"maxLength": 16,
57
57
"minLength": 1
58
58
},
59
59
"acquire": {
60
60
"type": "string",
61
61
"format": "uri"
62
62
}
63
63
},
64
-
"description": "Logical OR. Passes when at least one sub-rule passes, e.g. 'holds a sheet in any of these systems'."
64
+
"description": "Passes when at least one rule passes."
65
65
},
66
66
"not": {
67
67
"type": "object",
68
68
"required": [
69
69
"check",
70
70
"rule"
71
71
],
72
72
"properties": {
73
73
"rule": {
74
74
"ref": "#rule",
75
75
"type": "ref"
76
76
},
77
77
"check": {
78
78
"type": "string",
79
79
"const": "not"
80
80
},
81
81
"label": {
82
82
"type": "string",
83
83
"maxLength": 120
84
84
}
85
85
},
86
-
"description": "Logical NOT. Passes only when the sub-rule fails. Use for exclusion gates: 'has not already claimed this reward'."
86
+
"description": "Passes only when the nested rule fails."
87
87
},
88
88
"main": {
89
89
"key": "any",
90
90
"type": "record",
91
91
"record": {
92
92
"type": "object",
93
93
"required": [
94
94
"name",
95
95
"url",
96
96
"publisher",
97
97
"category",
98
98
"createdAt"
99
99
],
100
100
"properties": {
101
101
"url": {
102
102
"type": "string",
103
103
"format": "uri",
104
-
"description": "URL at which the game is played, downloaded, or accessed. This is a launch location, not the record's identity."
104
+
"description": "Where to play, download, or access the game."
105
105
},
106
106
"icon": {
107
107
"type": "blob",
108
108
"accept": [
109
109
"image/png",
110
110
"image/webp"
111
111
],
112
112
"maxSize": 512000,
113
-
"description": "Square icon for compact display contexts. Recommended 256x256 px."
113
+
"description": "Square icon. 256x256 recommended."
114
114
},
115
115
"name": {
116
116
"type": "string",
117
117
"maxLength": 100,
118
-
"description": "Display name of the experience as shown in portal listings."
118
+
"description": "Display name."
119
119
},
120
120
"tags": {
121
121
"type": "array",
122
122
"items": {
123
123
"type": "string",
124
124
"maxLength": 32
125
125
},
126
126
"maxLength": 12,
127
-
"description": "Freeform tags for search and discovery (e.g. 'arcade', 'pixel art', 'multiplayer')."
127
+
"description": "Freeform search tags."
128
128
},
129
129
"uses": {
130
130
"type": "array",
131
131
"items": {
132
132
"ref": "#recordUse",
133
133
"type": "ref"
134
134
},
135
135
"maxLength": 32,
136
-
"description": "Non-required records this game uses, understands, produces, validates, or wants portals to read. Use requires[] only for hard gates. Use uses[] for everything else: optional gameplay perks, character imports, cosmetics, bonuses, interoperability, stats, saves, activity, sessions, replays, leaderboards, labels, grants, validation, inventory, or profile context. The role and purpose fields explain whether the use is a player-facing benefit, a game-authored record, or a portal-readable display surface."
136
+
"description": "Other records this game reads or writes. Not required to play."
137
137
},
138
138
"cover": {
139
139
"type": "blob",
140
140
"accept": [
141
141
"image/png",
142
142
"image/jpeg",
143
143
"image/webp"
144
144
],
145
145
"maxSize": 4000000,
146
-
"description": "Primary cover image. Portals display this as the card thumbnail. Recommended ratio 16:9."
146
+
"description": "Card thumbnail image. 16:9 recommended."
147
147
},
148
148
"media": {
149
149
"type": "array",
150
150
"items": {
151
151
"ref": "#mediaItem",
152
152
"type": "ref"
153
153
},
154
154
"maxLength": 24,
155
-
"description": "Extended media gallery, screenshots, banners, key art, and posters a portal can show on a detail page. 'cover' and 'icon' above stay the primary card images; this is the deeper set, mirroring the rich galleries dedicated game catalogs keep."
155
+
"description": "Extra screenshots, banners, and key art."
156
156
},
157
157
"status": {
158
158
"type": "string",
159
-
"description": "Current availability. Known values: 'live', 'beta', 'coming' (announced but not yet playable), 'archived' (no longer available). Open set; portals may badge or filter on these.",
159
+
"description": "Current availability.",
160
160
"knownValues": [
161
161
"live",
162
162
"beta",
163
163
"coming",
164
164
"archived"
165
165
]
166
166
},
167
167
"catalog": {
168
168
"type": "array",
169
169
"items": {
170
170
"ref": "#catalogRef",
171
171
"type": "ref"
172
172
},
173
173
"maxLength": 8,
174
-
"description": "Optional links to records in other game catalogs that describe this same title, most often a Pentaract (games.gamesgamesgamesgames.game) entry. This is how a definition borrows rich metadata a dedicated catalog already maintains, genres, platforms, screenshots, release dates, ratings, instead of duplicating it here. Purely supplementary: evaluation never reads these, and the experience is complete without them. Cooperate, don't re-enter."
174
+
"description": "Links to matching entries in other game catalogs. Purely supplementary."
175
175
},
176
176
"tagline": {
177
177
"type": "string",
178
178
"maxLength": 200,
179
-
"description": "One-line summary shown beneath the title in card views."
179
+
"description": "One-line summary."
180
180
},
181
181
"category": {
182
182
"type": "string",
183
183
"maxLength": 30,
184
-
"description": "Broad type of experience, used for portal filtering and display. Known values: 'browser' (plays in a web browser), 'download' (downloaded and run locally), 'physical' (tabletop or in-person), 'other'. This is an open set, portals should tolerate values they do not recognise.",
184
+
"description": "Type of experience.",
185
185
"knownValues": [
186
186
"browser",
187
187
"download",
188
188
"physical",
189
189
"other"
190
190
]
191
191
},
192
192
"requires": {
193
193
"type": "array",
194
194
"items": {
195
195
"ref": "#rule",
196
196
"type": "ref"
197
197
},
198
198
"maxLength": 16,
199
-
"description": "Hard playability requirements. Every rule must pass before the game can be played as declared. Do not put recommendations, bonuses, or merely supported records here. Portals surface failures as actionable prompts using each rule's 'acquire' link."
199
+
"description": "Hard gates. Every rule must pass before a player can use this game."
200
200
},
201
201
"trailers": {
202
202
"type": "array",
203
203
"items": {
204
204
"ref": "#video",
205
205
"type": "ref"
206
206
},
207
207
"maxLength": 8,
208
-
"description": "Video trailers and gameplay clips. Hosted externally (YouTube, Vimeo, Twitch) or linked directly so portals can embed or link them."
208
+
"description": "Video trailers or gameplay clips."
209
209
},
210
210
"createdAt": {
211
211
"type": "string",
212
212
"format": "datetime"
213
213
},
214
214
"developer": {
215
215
"ref": "#party",
216
216
"type": "ref",
217
-
"description": "The studio or individual who built the experience. Prefer a DID when available so the developer is verifiable on the network."
217
+
"description": "The studio or person who made this."
218
218
},
219
219
"legacyIds": {
220
220
"type": "array",
221
221
"items": {
222
222
"type": "string",
223
223
"maxLength": 80
224
224
},
225
225
"maxLength": 12,
226
-
"description": "Stable identifiers used by older catalogues, score readers, or inventory integrations for this same game. These are compatibility aliases, not canonical identity."
226
+
"description": "Old IDs used for this same game by other catalogs or tools."
227
227
},
228
228
"publisher": {
229
229
"type": "string",
230
230
"format": "at-uri",
231
-
"description": "Stable AT-URI identifying the account that publishes and maintains this definition, normally at:// followed by the publishing DID. It must name the same repository that contains this record; portals should reject a mismatch."
231
+
"description": "AT-URI of the repo that owns this record. Must match this record's own repo."
232
232
},
233
233
"updatedAt": {
234
234
"type": "string",
235
235
"format": "datetime"
236
236
},
237
237
"releasedAt": {
238
238
"type": "string",
239
239
"format": "datetime",
240
-
"description": "When this experience became publicly available."
240
+
"description": "When this became available."
241
241
},
242
242
"description": {
243
243
"type": "string",
244
244
"maxLength": 2000,
245
-
"description": "Full description of the experience. Plain text; portals may render line breaks."
245
+
"description": "Full description."
246
246
},
247
247
"statNamespace": {
248
248
"type": "string",
249
249
"maxLength": 80,
250
-
"description": "Deprecated compatibility hint. Use uses[] with role 'activity' or 'stats' to describe player activity records and display metrics."
250
+
"description": "Deprecated. Use uses[] instead."
251
251
}
252
252
}
253
253
},
254
-
"description": "Published to the publisher's own repository, one record per game. Use a stable, readable record key and update that same record in place. The record's at:// URI is its canonical identity; 'url' is an access location and may change. A portal must not merge definitions solely because their URLs match."
254
+
"description": "One game definition per record, published in the developer's own repo. Keep the same rkey to update it in place."
255
255
},
256
256
"rule": {
257
257
"type": "object",
258
258
"required": [
259
259
"check"
260
260
],
261
261
"properties": {
262
262
"op": {
263
263
"type": "string",
264
-
"description": "For field: comparison operator.",
264
+
"description": "Comparison operator.",
265
265
"knownValues": [
266
266
"exists",
267
267
"notExists",
268
268
"isPlayer",
269
269
"eq",
270
270
"neq",
271
271
"gt",
272
272
"gte",
273
273
"lt",
274
274
"lte"
275
275
]
276
276
},
277
277
"nsid": {
278
278
"type": "string",
279
279
"format": "nsid",
280
-
"description": "Collection read by record, field, matches, or vouch."
280
+
"description": "Collection to read."
281
281
},
282
282
"rkey": {
283
283
"type": "string",
284
284
"maxLength": 50,
285
-
"description": "For field: record key, default 'self'; 'any' scans the collection."
285
+
"description": "Record key. Defaults to 'self'; 'any' scans the collection."
286
286
},
287
287
"rule": {
288
288
"ref": "#rule",
289
289
"type": "ref",
290
-
"description": "For not: nested rule to negate."
290
+
"description": "Nested rule for not."
291
291
},
292
292
"check": {
293
293
"type": "string",
294
-
"description": "Rule discriminator. Known values are open for forward compatibility; an unknown value must not pass.",
294
+
"description": "Rule type.",
295
295
"knownValues": [
296
296
"record",
297
297
"field",
298
298
"matches",
299
299
"vouch",
300
300
"all",
301
301
"any",
302
302
"not"
303
303
]
304
304
},
305
305
"field": {
306
306
"type": "string",
307
307
"maxLength": 200,
308
-
"description": "For field: dot-path to test."
308
+
"description": "Dot-path to test."
309
309
},
310
310
"label": {
311
311
"type": "string",
312
312
"maxLength": 120,
313
-
"description": "Human-readable requirement."
313
+
"description": "Why this is required."
314
314
},
315
315
"rules": {
316
316
"type": "array",
317
317
"items": {
318
318
"ref": "#rule",
319
319
"type": "ref"
320
320
},
321
321
"maxLength": 16,
322
322
"minLength": 1,
323
-
"description": "For all or any: nested rules."
323
+
"description": "Nested rules for all/any."
324
324
},
325
325
"value": {
326
326
"type": "unknown",
327
-
"description": "Comparison value for value-taking operators."
327
+
"description": "Comparison value."
328
328
},
329
+
"verify": {
330
+
"ref": "#rule",
331
+
"type": "ref",
332
+
"description": "Additional proof that must also pass when this rule is used in requires[]."
333
+
},
329
334
"acquire": {
330
335
"type": "string",
331
336
"format": "uri",
332
-
"description": "URL where a player can fulfil this requirement."
337
+
"description": "Where to get this."
333
338
},
334
339
"filters": {
335
340
"type": "array",
336
341
"items": {
337
342
"ref": "#filter",
338
343
"type": "ref"
339
344
},
340
345
"maxLength": 8,
341
346
"minLength": 1,
342
-
"description": "For matches or vouch: conditions that must hold on one record."
347
+
"description": "Conditions for matches or vouch."
343
348
},
344
349
"authority": {
345
350
"type": "string",
346
351
"format": "did",
347
-
"description": "For vouch: stable DID of the record issuer."
352
+
"description": "DID of the trusted issuer, for vouch."
348
353
}
349
354
},
350
-
"description": "One recursive playability rule, discriminated by 'check'. Lexicon does not permit a union as a top-level definition, so this object carries the fields used by all seven variants. Evaluators enforce each variant's required fields: record(check,nsid); field(check,nsid,field,op); matches(check,nsid,filters); vouch(check,authority,nsid); all/any(check,rules); not(check,rule). Unknown check values evaluate as unknown, never as pass."
355
+
"description": "One playability rule. 'check' selects the variant: record, field, matches, vouch, all, any, or not. A rule may include verify for an additional required proof. An unknown check must never pass."
351
356
},
352
357
"field": {
353
358
"type": "object",
354
359
"required": [
355
360
"check",
356
361
"nsid",
357
362
"field",
358
363
"op"
359
364
],
360
365
"properties": {
361
366
"op": {
362
367
"type": "string",
363
-
"description": "How to test the field. exists/notExists check presence; eq/neq/gt/gte/lt/lte compare to value; isPlayer matches the signed-in player by their account ID (DID) and takes no value.",
368
+
"description": "Comparison operator.",
364
369
"knownValues": [
365
370
"exists",
366
371
"notExists",
367
372
"isPlayer",
368
373
"eq",
369
374
"neq",
370
375
"gt",
371
376
"gte",
372
377
"lt",
373
378
"lte"
374
379
]
375
380
},
376
381
"nsid": {
377
382
"type": "string",
378
383
"format": "nsid"
379
384
},
380
385
"rkey": {
381
386
"type": "string",
382
387
"maxLength": 50,
383
-
"description": "Record key. Defaults to 'self'. Use 'any' to pass if any record in the collection satisfies the condition."
388
+
"description": "Record key. Defaults to 'self'; 'any' scans the collection."
384
389
},
385
390
"check": {
386
391
"type": "string",
387
392
"const": "field"
388
393
},
389
394
"field": {
390
395
"type": "string",
391
396
"maxLength": 200,
392
-
"description": "Dot-path to the property to evaluate (e.g. 'score.total', 'level'). Array traversal is not supported."
397
+
"description": "Dot-path to the property to test."
393
398
},
394
399
"label": {
395
400
"type": "string",
396
401
"maxLength": 120
397
402
},
398
403
"value": {
399
404
"type": "unknown",
400
-
"description": "Comparison value for value-taking operators. Strings, numbers, and booleans are accepted; numeric operators coerce values to numbers."
405
+
"description": "Comparison value."
401
406
},
402
407
"acquire": {
403
408
"type": "string",
404
409
"format": "uri"
405
410
}
406
411
},
407
-
"description": "Fetches a record from the player's repository and tests one field. Covers required fields, score thresholds, flags, and enum matches."
412
+
"description": "Passes if a field on the player's record meets a condition."
408
413
},
409
414
"party": {
410
415
"type": "object",
411
416
"required": [
412
417
"name"
413
418
],
414
419
"properties": {
415
420
"did": {
416
421
"type": "string",
417
422
"format": "did",
418
-
"description": "AT Protocol DID of this entity's account, if they have one. On an atmosphere.games-managed provisional definition, this is also the DID expected to publish the canonical developer-owned definition."
423
+
"description": "DID of this entity's account, if it has one."
419
424
},
420
425
"url": {
421
426
"type": "string",
422
427
"format": "uri",
423
-
"description": "Website for this entity."
428
+
"description": "Website."
424
429
},
425
430
"name": {
426
431
"type": "string",
427
432
"maxLength": 100,
428
-
"description": "Display name of the entity."
433
+
"description": "Display name."
429
434
}
430
435
},
431
-
"description": "A named entity (developer, publisher, studio) associated with the experience."
436
+
"description": "An entity associated with the experience, such as its developer."
432
437
},
433
438
"video": {
434
439
"type": "object",
435
440
"required": [
436
441
"title"
437
442
],
438
443
"properties": {
439
444
"url": {
440
445
"type": "string",
441
446
"format": "uri",
442
-
"description": "Full URL to the video, used when no platform/videoId is given."
447
+
"description": "Direct URL, used when no platform/videoId is given."
443
448
},
444
449
"title": {
445
450
"type": "string",
446
451
"maxLength": 120,
447
-
"description": "Label for the clip (e.g. 'Launch Trailer')."
452
+
"description": "Clip title."
448
453
},
449
454
"videoId": {
450
455
"type": "string",
451
456
"maxLength": 120,
452
-
"description": "Platform video id (e.g. a YouTube id). Combined with 'platform' to build an embed."
457
+
"description": "Platform video id."
453
458
},
454
459
"platform": {
455
460
"type": "string",
456
-
"description": "Where the video lives. 'direct' means the url points straight at a video file or page.",
461
+
"description": "Where the video lives.",
457
462
"knownValues": [
458
463
"youtube",
459
464
"vimeo",
460
465
"twitch",
461
466
"direct"
462
467
]
463
468
},
464
469
"thumbnail": {
465
470
"type": "blob",
466
471
"accept": [
467
472
"image/png",
468
473
"image/jpeg",
469
474
"image/webp"
470
475
],
471
476
"maxSize": 2000000,
472
-
"description": "Optional still frame shown before the video plays."
477
+
"description": "Still frame shown before playing."
473
478
}
474
479
},
475
-
"description": "A trailer or gameplay clip. Provide a platform + videoId (preferred, so portals embed natively) or a direct url."
480
+
"description": "A trailer or gameplay clip."
476
481
},
477
482
"vouch": {
478
483
"type": "object",
479
484
"required": [
480
485
"check",
481
486
"authority",
482
487
"nsid"
483
488
],
484
489
"properties": {
485
490
"nsid": {
486
491
"type": "string",
487
492
"format": "nsid",
488
-
"description": "The kind of record to look for in that account's repo (e.g. equipment.rpg.give)."
493
+
"description": "Collection to look for in the authority's repo."
489
494
},
490
495
"check": {
491
496
"type": "string",
492
497
"const": "vouch"
493
498
},
494
499
"label": {
495
500
"type": "string",
496
501
"maxLength": 120
497
502
},
498
503
"acquire": {
499
504
"type": "string",
500
505
"format": "uri"
501
506
},
502
507
"filters": {
503
508
"type": "array",
504
509
"items": {
505
510
"ref": "#filter",
506
511
"type": "ref"
507
512
},
508
513
"maxLength": 8,
509
-
"description": "Conditions the record must meet, written exactly like a matches rule. Put the 'isPlayer' operator on the field that names the player (e.g. recipient) to require the record reference the signed-in player; add value conditions (e.g. item equals jet_pack) to pin a specific grant. With no filters, any record of this kind on the authority's repo passes."
514
+
"description": "Conditions the record must meet. Use 'isPlayer' to require it name the signed-in player."
510
515
},
511
516
"authority": {
512
517
"type": "string",
513
518
"format": "did",
514
-
"description": "Stable DID of the account that issues the record (for example an item provider, guild, or tournament organiser). Handles are not accepted because they are mutable. A player can never be their own authority."
519
+
"description": "DID of the account that issues the record."
515
520
}
516
521
},
517
-
"description": "Passes when a trusted account's repo holds a record that meets the given conditions, optionally one that references the signed-in player. Because the record lives on the issuer's repo, not the player's, it cannot be forged. Use it for item grants, awards, memberships, approvals, licences, or any record one account publishes about another. Example: 'games.protoimsg.app has an equipment.rpg.give whose recipient is this player and whose item is jet_pack.'"
522
+
"description": "Passes if a trusted authority's repo holds a matching record. Use for grants, awards, or approvals one account issues about another."
518
523
},
519
524
"filter": {
520
525
"type": "object",
521
526
"required": [
522
527
"field",
523
528
"op"
524
529
],
525
530
"properties": {
526
531
"op": {
527
532
"type": "string",
528
-
"description": "How to test the field. exists/notExists check presence; eq/neq/gt/gte/lt/lte compare to value; isPlayer matches the signed-in player by their account ID (DID) and takes no value.",
533
+
"description": "Comparison operator.",
529
534
"knownValues": [
530
535
"exists",
531
536
"notExists",
532
537
"isPlayer",
533
538
"eq",
534
539
"neq",
535
540
"gt",
536
541
"gte",
537
542
"lt",
538
543
"lte"
539
544
]
540
545
},
541
546
"field": {
542
547
"type": "string",
543
548
"maxLength": 200,
544
549
"description": "Dot-path to the field."
545
550
},
546
551
"value": {
547
552
"type": "unknown",
548
-
"description": "Comparison value for value-taking operators. Strings, numbers, and booleans are accepted; numeric operators coerce values to numbers."
553
+
"description": "Comparison value."
549
554
}
550
555
},
551
-
"description": "A single condition on a record's contents, used by matches and vouch. Most operators compare the field to a value. The special 'isPlayer' operator takes no value and passes when the field identifies the signed-in player by their account ID (DID). It accepts the DID directly or an at:// URI whose repo is the player, and never matches on a handle, since handles can change. This is how a static definition points at whoever is reading it."
556
+
"description": "One field condition, used by matches and vouch. 'isPlayer' passes when the field names the signed-in player."
552
557
},
553
558
"record": {
554
559
"type": "object",
555
560
"required": [
556
561
"check",
557
562
"nsid"
558
563
],
559
564
"properties": {
560
565
"nsid": {
561
566
"type": "string",
562
567
"format": "nsid",
563
-
"description": "Collection to check on the player's PDS (e.g. 'actor.rpg.sprite')."
568
+
"description": "Collection to check."
564
569
},
565
570
"check": {
566
571
"type": "string",
567
572
"const": "record"
568
573
},
569
574
"label": {
570
575
"type": "string",
571
576
"maxLength": 120,
572
-
"description": "Human-readable requirement shown when gated (e.g. 'An rpg.actor sprite')."
577
+
"description": "Why this is required."
573
578
},
574
579
"acquire": {
575
580
"type": "string",
576
581
"format": "uri",
577
-
"description": "URL to send players who fail, so they can fulfil the requirement."
582
+
"description": "Where to get this."
578
583
}
579
584
},
580
-
"description": "Passes if the player's PDS holds at least one record in the given collection. The simplest gate: 'does this person participate in this lexicon at all?'"
585
+
"description": "Passes if the player holds any record in this collection."
581
586
},
582
587
"matches": {
583
588
"type": "object",
584
589
"required": [
585
590
"check",
586
591
"nsid",
587
592
"filters"
588
593
],
589
594
"properties": {
590
595
"nsid": {
591
596
"type": "string",
592
597
"format": "nsid"
593
598
},
594
599
"check": {
595
600
"type": "string",
596
601
"const": "matches"
597
602
},
598
603
"label": {
599
604
"type": "string",
600
605
"maxLength": 120
601
606
},
602
607
"acquire": {
603
608
"type": "string",
604
609
"format": "uri"
605
610
},
606
611
"filters": {
607
612
"type": "array",
608
613
"items": {
609
614
"ref": "#filter",
610
615
"type": "ref"
611
616
},
612
617
"maxLength": 8,
613
618
"minLength": 1,
614
-
"description": "All filters must match on the same record (AND semantics within one record)."
619
+
"description": "All filters must match on the same record."
615
620
}
616
621
},
617
-
"description": "Scans the player's collection and passes if any single record satisfies all filters simultaneously. Use to find a record by content rather than by key, e.g. 'holds item X', 'has membership tier Y'."
622
+
"description": "Passes if one record satisfies all filters."
618
623
},
619
624
"mediaItem": {
620
625
"type": "object",
621
626
"required": [
622
627
"image"
623
628
],
624
629
"properties": {
625
630
"alt": {
626
631
"type": "string",
627
632
"maxLength": 300,
628
-
"description": "Accessibility caption describing the image."
633
+
"description": "Accessibility caption."
629
634
},
630
635
"type": {
631
636
"type": "string",
632
-
"description": "What kind of image this is, so portals can place it (e.g. 'banner' across the top, 'screenshot' in a gallery).",
637
+
"description": "Kind of image.",
633
638
"knownValues": [
634
639
"screenshot",
635
640
"banner",
636
641
"artwork",
637
642
"poster",
638
643
"background",
639
644
"logo",
640
645
"thumbnail"
641
646
]
642
647
},
643
648
"image": {
644
649
"type": "blob",
645
650
"accept": [
646
651
"image/png",
647
652
"image/jpeg",
648
653
"image/webp",
649
654
"image/gif"
650
655
],
651
656
"maxSize": 8000000,
652
-
"description": "The image blob, stored on the developer's PDS."
657
+
"description": "The image blob."
653
658
},
654
659
"width": {
655
660
"type": "integer",
656
-
"description": "Pixel width, if known, lets portals reserve layout space before the blob loads."
661
+
"description": "Pixel width, if known."
657
662
},
658
663
"height": {
659
664
"type": "integer",
660
665
"description": "Pixel height, if known."
661
666
}
662
667
},
663
-
"description": "One image in the experience's media gallery."
668
+
"description": "One image in the media gallery."
664
669
},
665
670
"recordUse": {
666
671
"type": "object",
667
672
"required": [
668
673
"role",
669
674
"nsid"
670
675
],
671
676
"properties": {
672
677
"nsid": {
673
678
"type": "string",
674
679
"format": "nsid",
675
-
"description": "Collection to read or use."
680
+
"description": "Collection to read."
676
681
},
677
682
"path": {
678
683
"type": "string",
679
684
"maxLength": 200,
680
-
"description": "Optional dot-path to the relevant payload within the record, e.g. 'data' inside actor.rpg.stats/clunscannon."
685
+
"description": "Dot-path to the relevant value inside the record."
681
686
},
682
687
"rkey": {
683
688
"type": "string",
684
689
"maxLength": 50,
685
-
"description": "Record key to read from a player's repo. Defaults to 'self'; use 'any' when a portal should scan the collection."
690
+
"description": "Record key. Defaults to 'self'; 'any' scans the collection."
686
691
},
687
692
"role": {
688
693
"type": "string",
689
-
"description": "How this record is used. Optional gameplay roles such as character, perk, cosmetic, bonus, and interop are shown as Optional Records in player-facing directories. Data roles such as stats, activity, leaderboard, save, replay, session, profile, inventory, grant, validation, and label describe records portals/tools may read, summarize, or verify without presenting them as optional perks.",
694
+
"description": "What this record is used for.",
690
695
"knownValues": [
691
696
"character",
692
697
"perk",
693
698
"cosmetic",
694
-
"bonus",
695
699
"interop",
696
700
"stats",
697
701
"activity",
698
702
"leaderboard",
699
-
"save",
700
-
"replay",
703
+
"profile",
701
704
"session",
702
-
"profile",
703
705
"inventory",
704
-
"grant",
705
-
"validation",
706
-
"label",
707
706
"other"
708
707
]
709
708
},
710
709
"label": {
711
710
"type": "string",
712
711
"maxLength": 120,
713
-
"description": "Human label for this use."
712
+
"description": "Short label for this use."
714
713
},
715
714
"verify": {
716
715
"ref": "#rule",
717
716
"type": "ref",
718
-
"description": "Optional rule that detects, authenticates, or validates this use. For player-benefit roles, it badges whether the benefit is available. For data roles, it badges whether the referenced record is trusted."
717
+
"description": "Optional rule confirming this use is genuine."
719
718
},
720
719
"acquire": {
721
720
"type": "string",
722
721
"format": "uri",
723
-
"description": "Optional URL where a player can create, obtain, or manage this record."
722
+
"description": "Where to create or manage this record."
724
723
},
725
724
"filters": {
726
725
"type": "array",
727
726
"items": {
728
727
"ref": "#filter",
729
728
"type": "ref"
730
729
},
731
730
"maxLength": 8,
732
-
"description": "Optional filters used when rkey is 'any' or authority is set. Conditions must match on one record."
731
+
"description": "Conditions to match, used with rkey 'any' or an authority."
733
732
},
734
733
"purpose": {
735
734
"type": "string",
736
735
"maxLength": 300,
737
-
"description": "Plain-language reason this record matters. This should say whether it unlocks a benefit, imports player data, stores progress, validates a score, or powers portal display."
736
+
"description": "Why this record matters."
738
737
},
739
738
"authority": {
740
739
"type": "string",
741
740
"format": "did",
742
-
"description": "When the referenced record lives in a trusted authority repo instead of the player's repo, this DID names that repo."
741
+
"description": "DID of the repo holding this record, if not the player's own."
743
742
}
744
743
},
745
-
"description": "One non-required AT Protocol record relationship for this game. This single shape covers optional gameplay records and portal-readable data. Use role 'character' when a record identifies or renders the player in-game, role 'perk' when it grants an optional benefit, role 'stats' for progress/score records, role 'activity' for participation records, and role 'profile' for profile/context records. Hard gates still belong in requires[]."
744
+
"description": "An optional record this game reads or writes. Not required to play; see requires[] for hard gates."
746
745
},
747
746
"catalogRef": {
748
747
"type": "object",
749
748
"required": [
750
749
"uri"
751
750
],
752
751
"properties": {
753
752
"uri": {
754
753
"type": "string",
755
754
"format": "at-uri",
756
-
"description": "AT-URI of the external catalog record (e.g. at://did:web:gamesgamesgamesgames.games/games.gamesgamesgamesgames.game/3mgjih2bzdp2l)."
755
+
"description": "AT-URI of the external catalog record."
757
756
},
758
757
"name": {
759
758
"type": "string",
760
759
"maxLength": 100,
761
-
"description": "Display name of the referenced entry, so portals can show it without resolving the record."
760
+
"description": "Display name of the referenced entry."
762
761
},
763
762
"borrow": {
764
763
"type": "array",
765
764
"items": {
766
765
"type": "string",
767
766
"knownValues": [
768
767
"media",
769
768
"trailers",
770
769
"genres",
771
770
"platforms",
772
771
"releases",
773
772
"ratings",
774
773
"description",
775
774
"summary"
776
775
]
777
776
},
778
777
"maxLength": 8,
779
-
"description": "Advisory hints naming which parts of the referenced record a portal may display alongside this experience (e.g. pull screenshots and genres from Pentaract rather than duplicating them here). Display guidance only, never affects eligibility."
778
+
"description": "Which parts of the referenced record a portal may show alongside this one."
780
779
},
781
780
"source": {
782
781
"type": "string",
783
782
"maxLength": 60,
784
-
"description": "Short label for the catalog this points at (e.g. 'pentaract')."
783
+
"description": "Short name of the catalog."
785
784
}
786
785
},
787
-
"description": "A pointer to a record in another catalog that already describes this title in depth, most usefully a games.gamesgamesgamesgames.game record on The Pentaract, which carries genres, platforms, release dates, age ratings, and a full media gallery. The definition stays self-contained (eligibility never depends on it); the reference simply lets a portal fetch that richer metadata and show it alongside, so we build on existing game databases instead of re-entering everything."
786
+
"description": "A link to a matching entry in another game catalog. Purely supplementary; never affects eligibility."
788
787
}
789
788
},
790
789
"$type": "com.atproto.lexicon.schema",
791
790
"lexicon": 1,
792
-
"description": "An AT Protocol game definition: a publisher's self-contained declaration of a playable experience, its presentation, the records a player must hold to play, and records the game can optionally use. Evaluators resolve requirements through AT Protocol repository reads; no game server or central eligibility database is required."
791
+
"description": "A game definition: what it is, what a player needs to play it, and what records it uses."
793
792
}