games.atmosphere.define
Schema Diff
+200 -5
Compatibility Analysis
Breaking Changes Detected
5 breaking changes, 61 non-breaking changes.
Breaking Changes (5)
- RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "games.atmosphere.define#referenceDisplay", src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.key", kind: "prop", name: Some("key") }
- RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "games.atmosphere.define#referenceDisplay", src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.path", kind: "prop", name: Some("path") }
- RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "games.atmosphere.define#referenceDisplay", src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.label", kind: "prop", name: Some("label") }
- RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "games.atmosphere.define#reference", src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.role", kind: "prop", name: Some("role") }
- RequiredEdgeAdded RequiredEdgeAdded { vertex_id: "games.atmosphere.define#reference", src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.nsid", kind: "prop", name: Some("nsid") }
Non-Breaking Changes (61)
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.acquire" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.authority" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.display" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.display:items" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.filters" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.filters:items" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.label" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.nsid" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.path" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.purpose" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.rkey" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.role" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#reference.verify" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.aggregate" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.divideBy" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.format" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.key" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.label" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.maximumFractionDigits" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.multiplyBy" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.path" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.priority" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.unit" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define:body.legacyIds" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define:body.legacyIds:items" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define:body.migratedFromAtUri" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define:body.references" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define:body.references:items" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define:body.replaces" }
- AddedVertex AddedVertex { vertex_id: "games.atmosphere.define:body.replaces:items" }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.acquire", kind: "prop", name: Some("acquire") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.authority", kind: "prop", name: Some("authority") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.display", kind: "prop", name: Some("display") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.filters", kind: "prop", name: Some("filters") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.label", kind: "prop", name: Some("label") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.path", kind: "prop", name: Some("path") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.purpose", kind: "prop", name: Some("purpose") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.rkey", kind: "prop", name: Some("rkey") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.verify", kind: "prop", name: Some("verify") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference.display", tgt: "games.atmosphere.define#reference.display:items", kind: "items", name: None }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference.display:items", tgt: "games.atmosphere.define#referenceDisplay", kind: "ref", name: None }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference.filters", tgt: "games.atmosphere.define#reference.filters:items", kind: "items", name: None }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference.filters:items", tgt: "games.atmosphere.define#filter", kind: "ref", name: None }
- AddedEdge AddedEdge { src: "games.atmosphere.define#reference.verify", tgt: "games.atmosphere.define#rule", kind: "ref", name: None }
- AddedEdge AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.aggregate", kind: "prop", name: Some("aggregate") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.divideBy", kind: "prop", name: Some("divideBy") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.format", kind: "prop", name: Some("format") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.maximumFractionDigits", kind: "prop", name: Some("maximumFractionDigits") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.multiplyBy", kind: "prop", name: Some("multiplyBy") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.priority", kind: "prop", name: Some("priority") }
- AddedEdge AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.unit", kind: "prop", name: Some("unit") }
- AddedEdge AddedEdge { src: "games.atmosphere.define:body", tgt: "games.atmosphere.define:body.legacyIds", kind: "prop", name: Some("legacyIds") }
- AddedEdge AddedEdge { src: "games.atmosphere.define:body", tgt: "games.atmosphere.define:body.migratedFromAtUri", kind: "prop", name: Some("migratedFromAtUri") }
- AddedEdge AddedEdge { src: "games.atmosphere.define:body", tgt: "games.atmosphere.define:body.references", kind: "prop", name: Some("references") }
- AddedEdge AddedEdge { src: "games.atmosphere.define:body", tgt: "games.atmosphere.define:body.replaces", kind: "prop", name: Some("replaces") }
- AddedEdge AddedEdge { src: "games.atmosphere.define:body.legacyIds", tgt: "games.atmosphere.define:body.legacyIds:items", kind: "items", name: None }
- AddedEdge AddedEdge { src: "games.atmosphere.define:body.references", tgt: "games.atmosphere.define:body.references:items", kind: "items", name: None }
- AddedEdge AddedEdge { src: "games.atmosphere.define:body.references:items", tgt: "games.atmosphere.define#reference", kind: "ref", name: None }
- AddedEdge AddedEdge { src: "games.atmosphere.define:body.replaces", tgt: "games.atmosphere.define:body.replaces:items", kind: "items", name: None }
Migration Guidance
Added Elements
AddedVertex { vertex_id: "games.atmosphere.define#reference" }AddedVertex { vertex_id: "games.atmosphere.define#reference.acquire" }AddedVertex { vertex_id: "games.atmosphere.define#reference.authority" }AddedVertex { vertex_id: "games.atmosphere.define#reference.display" }AddedVertex { vertex_id: "games.atmosphere.define#reference.display:items" }AddedVertex { vertex_id: "games.atmosphere.define#reference.filters" }AddedVertex { vertex_id: "games.atmosphere.define#reference.filters:items" }AddedVertex { vertex_id: "games.atmosphere.define#reference.label" }AddedVertex { vertex_id: "games.atmosphere.define#reference.nsid" }AddedVertex { vertex_id: "games.atmosphere.define#reference.path" }AddedVertex { vertex_id: "games.atmosphere.define#reference.purpose" }AddedVertex { vertex_id: "games.atmosphere.define#reference.rkey" }AddedVertex { vertex_id: "games.atmosphere.define#reference.role" }AddedVertex { vertex_id: "games.atmosphere.define#reference.verify" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.aggregate" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.divideBy" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.format" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.key" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.label" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.maximumFractionDigits" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.multiplyBy" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.path" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.priority" }AddedVertex { vertex_id: "games.atmosphere.define#referenceDisplay.unit" }AddedVertex { vertex_id: "games.atmosphere.define:body.legacyIds" }AddedVertex { vertex_id: "games.atmosphere.define:body.legacyIds:items" }AddedVertex { vertex_id: "games.atmosphere.define:body.migratedFromAtUri" }AddedVertex { vertex_id: "games.atmosphere.define:body.references" }AddedVertex { vertex_id: "games.atmosphere.define:body.references:items" }AddedVertex { vertex_id: "games.atmosphere.define:body.replaces" }AddedVertex { vertex_id: "games.atmosphere.define:body.replaces:items" }
Additional Notes
- Breaking: RequiredEdgeAdded { vertex_id: "games.atmosphere.define#referenceDisplay", src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.key", kind: "prop", name: Some("key") }
- Breaking: RequiredEdgeAdded { vertex_id: "games.atmosphere.define#referenceDisplay", src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.path", kind: "prop", name: Some("path") }
- Breaking: RequiredEdgeAdded { vertex_id: "games.atmosphere.define#referenceDisplay", src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.label", kind: "prop", name: Some("label") }
- Breaking: RequiredEdgeAdded { vertex_id: "games.atmosphere.define#reference", src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.role", kind: "prop", name: Some("role") }
- Breaking: RequiredEdgeAdded { vertex_id: "games.atmosphere.define#reference", src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.nsid", kind: "prop", name: Some("nsid") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.acquire", kind: "prop", name: Some("acquire") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.authority", kind: "prop", name: Some("authority") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.display", kind: "prop", name: Some("display") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.filters", kind: "prop", name: Some("filters") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.label", kind: "prop", name: Some("label") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.path", kind: "prop", name: Some("path") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.purpose", kind: "prop", name: Some("purpose") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.rkey", kind: "prop", name: Some("rkey") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference", tgt: "games.atmosphere.define#reference.verify", kind: "prop", name: Some("verify") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference.display", tgt: "games.atmosphere.define#reference.display:items", kind: "items", name: None }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference.display:items", tgt: "games.atmosphere.define#referenceDisplay", kind: "ref", name: None }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference.filters", tgt: "games.atmosphere.define#reference.filters:items", kind: "items", name: None }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference.filters:items", tgt: "games.atmosphere.define#filter", kind: "ref", name: None }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#reference.verify", tgt: "games.atmosphere.define#rule", kind: "ref", name: None }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.aggregate", kind: "prop", name: Some("aggregate") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.divideBy", kind: "prop", name: Some("divideBy") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.format", kind: "prop", name: Some("format") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.maximumFractionDigits", kind: "prop", name: Some("maximumFractionDigits") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.multiplyBy", kind: "prop", name: Some("multiplyBy") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.priority", kind: "prop", name: Some("priority") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define#referenceDisplay", tgt: "games.atmosphere.define#referenceDisplay.unit", kind: "prop", name: Some("unit") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define:body", tgt: "games.atmosphere.define:body.legacyIds", kind: "prop", name: Some("legacyIds") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define:body", tgt: "games.atmosphere.define:body.migratedFromAtUri", kind: "prop", name: Some("migratedFromAtUri") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define:body", tgt: "games.atmosphere.define:body.references", kind: "prop", name: Some("references") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define:body", tgt: "games.atmosphere.define:body.replaces", kind: "prop", name: Some("replaces") }
- Non-breaking: AddedEdge { src: "games.atmosphere.define:body.legacyIds", tgt: "games.atmosphere.define:body.legacyIds:items", kind: "items", name: None }
- Non-breaking: AddedEdge { src: "games.atmosphere.define:body.references", tgt: "games.atmosphere.define:body.references:items", kind: "items", name: None }
- Non-breaking: AddedEdge { src: "games.atmosphere.define:body.references:items", tgt: "games.atmosphere.define#reference", kind: "ref", name: None }
- Non-breaking: AddedEdge { src: "games.atmosphere.define:body.replaces", tgt: "games.atmosphere.define:body.replaces:items", kind: "items", 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
33
"description": "Logical AND. Passes only when every sub-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
64
"description": "Logical OR. Passes when at least one sub-rule passes, e.g. 'holds a sheet in any of these systems'."
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
86
"description": "Logical NOT. Passes only when the sub-rule fails. Use for exclusion gates: 'has not already claimed this reward'."
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
104
"description": "URL at which the game is played, downloaded, or accessed. This is a launch location, not the record's identity."
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
113
"description": "Square icon for compact display contexts. Recommended 256x256 px."
114
114
},
115
115
"name": {
116
116
"type": "string",
117
117
"maxLength": 100,
118
118
"description": "Display name of the experience as shown in portal listings."
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
127
"description": "Freeform tags for search and discovery (e.g. 'arcade', 'pixel art', 'multiplayer')."
128
128
},
129
129
"cover": {
130
130
"type": "blob",
131
131
"accept": [
132
132
"image/png",
133
133
"image/jpeg",
134
134
"image/webp"
135
135
],
136
136
"maxSize": 4000000,
137
137
"description": "Primary cover image. Portals display this as the card thumbnail. Recommended ratio 16:9."
138
138
},
139
139
"media": {
140
140
"type": "array",
141
141
"items": {
142
142
"ref": "#mediaItem",
143
143
"type": "ref"
144
144
},
145
145
"maxLength": 24,
146
146
"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."
147
147
},
148
148
"status": {
149
149
"type": "string",
150
150
"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.",
151
151
"knownValues": [
152
152
"live",
153
153
"beta",
154
154
"coming",
155
155
"archived"
156
156
]
157
157
},
158
158
"catalog": {
159
159
"type": "array",
160
160
"items": {
161
161
"ref": "#catalogRef",
162
162
"type": "ref"
163
163
},
164
164
"maxLength": 8,
165
165
"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."
166
166
},
167
167
"tagline": {
168
168
"type": "string",
169
169
"maxLength": 200,
170
170
"description": "One-line summary shown beneath the title in card views."
171
171
},
172
172
"category": {
173
173
"type": "string",
174
174
"maxLength": 30,
175
175
"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.",
176
176
"knownValues": [
177
177
"browser",
178
178
"download",
179
179
"physical",
180
180
"other"
181
181
]
182
182
},
183
183
"includes": {
184
184
"type": "array",
185
185
"items": {
186
186
"ref": "#lexiconRef",
187
187
"type": "ref"
188
188
},
189
189
"maxLength": 24,
190
-
"description": "Records this game can use when present but never needs in order to run. Portals may show these as optional interoperability, import, cosmetic, or bonus features. They never gate play."
190
+
"description": "Optional player-facing records that change or enrich the experience when present. Use includes[] when the game itself can do something extra for the player: import a character, unlock a cosmetic, grant a bonus, recognise an item, or enable an interoperability perk. An include may have verify logic so portals can badge whether that optional benefit is available. Includes never gate basic play; put hard gates in requires[]. Do not use includes[] merely to tell portals how to read stats, saves, activity, or leaderboard data; use references[] for that."
191
191
},
192
+
"replaces": {
193
+
"type": "array",
194
+
"items": {
195
+
"type": "string",
196
+
"format": "at-uri"
197
+
},
198
+
"maxLength": 12,
199
+
"description": "Definition records this record intentionally supersedes. Used for ownership claims, rekeys, and mergers without encoding platform-specific migration language. A portal may trust a claim only when the replaced source definition names this record's repo DID as developer.did."
200
+
},
192
201
"requires": {
193
202
"type": "array",
194
203
"items": {
195
204
"ref": "#rule",
196
205
"type": "ref"
197
206
},
198
207
"maxLength": 16,
199
208
"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."
200
209
},
201
210
"trailers": {
202
211
"type": "array",
203
212
"items": {
204
213
"ref": "#video",
205
214
"type": "ref"
206
215
},
207
216
"maxLength": 8,
208
217
"description": "Video trailers and gameplay clips. Hosted externally (YouTube, Vimeo, Twitch) or linked directly so portals can embed or link them."
209
218
},
210
219
"createdAt": {
211
220
"type": "string",
212
221
"format": "datetime"
213
222
},
214
223
"developer": {
215
224
"ref": "#party",
216
225
"type": "ref",
217
226
"description": "The studio or individual who built the experience. Prefer a DID when available so the developer is verifiable on the network."
218
227
},
228
+
"legacyIds": {
229
+
"type": "array",
230
+
"items": {
231
+
"type": "string",
232
+
"maxLength": 80
233
+
},
234
+
"maxLength": 12,
235
+
"description": "Stable identifiers used by older catalogues, score readers, or inventory integrations for this same game. These are compatibility aliases, not canonical identity."
236
+
},
219
237
"publisher": {
220
238
"type": "string",
221
239
"format": "at-uri",
222
240
"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."
223
241
},
224
242
"updatedAt": {
225
243
"type": "string",
226
244
"format": "datetime"
227
245
},
246
+
"references": {
247
+
"type": "array",
248
+
"items": {
249
+
"ref": "#reference",
250
+
"type": "ref"
251
+
},
252
+
"maxLength": 24,
253
+
"description": "Non-gating records that describe where this game's portable data lives and how portals or tools may read it. Use references[] for stats, saves, activity, sessions, replays, leaderboard entries, validation records, labels, grants, profiles, or other context that summarizes play. A reference is a reading recipe, not a player benefit: it may carry display metrics and an optional verify rule for trusted presentation, but it does not by itself say the game uses that record during play. Put hard access rules in requires[] and optional gameplay/perk integrations in includes[]."
254
+
},
228
255
"releasedAt": {
229
256
"type": "string",
230
257
"format": "datetime",
231
258
"description": "When this experience became publicly available."
232
259
},
233
260
"description": {
234
261
"type": "string",
235
262
"maxLength": 2000,
236
263
"description": "Full description of the experience. Plain text; portals may render line breaks."
237
264
},
238
265
"statNamespace": {
239
266
"type": "string",
240
267
"maxLength": 80,
241
-
"description": "Collection NSID under which this experience writes player activity. Portals count unique DIDs holding records here to derive player counts, no telemetry required."
268
+
"description": "Deprecated compatibility hint. Use references[] with role 'activity' or 'stats' to describe player activity records and display metrics."
269
+
},
270
+
"migratedFromAtUri": {
271
+
"type": "string",
272
+
"format": "at-uri",
273
+
"description": "Deprecated compatibility field. Use 'replaces' for clean definition lineage. Exact URI of a previous definition republished into this repository during an ownership claim. Portals must accept this lineage only when the source definition names this repository DID as developer.did; lineage by itself is not proof of ownership."
242
274
}
243
275
}
244
276
},
245
277
"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."
246
278
},
247
279
"rule": {
248
280
"type": "object",
249
281
"required": [
250
282
"check"
251
283
],
252
284
"properties": {
253
285
"op": {
254
286
"type": "string",
255
287
"description": "For field: comparison operator.",
256
288
"knownValues": [
257
289
"exists",
258
290
"notExists",
259
291
"isViewer",
260
292
"eq",
261
293
"neq",
262
294
"gt",
263
295
"gte",
264
296
"lt",
265
297
"lte"
266
298
]
267
299
},
268
300
"nsid": {
269
301
"type": "string",
270
302
"format": "nsid",
271
303
"description": "Collection read by record, field, matches, or vouch."
272
304
},
273
305
"rkey": {
274
306
"type": "string",
275
307
"maxLength": 50,
276
308
"description": "For field: record key, default 'self'; 'any' scans the collection."
277
309
},
278
310
"rule": {
279
311
"ref": "#rule",
280
312
"type": "ref",
281
313
"description": "For not: nested rule to negate."
282
314
},
283
315
"check": {
284
316
"type": "string",
285
317
"description": "Rule discriminator. Known values are open for forward compatibility; an unknown value must not pass.",
286
318
"knownValues": [
287
319
"record",
288
320
"field",
289
321
"matches",
290
322
"vouch",
291
323
"all",
292
324
"any",
293
325
"not"
294
326
]
295
327
},
296
328
"field": {
297
329
"type": "string",
298
330
"maxLength": 200,
299
331
"description": "For field: dot-path to test."
300
332
},
301
333
"label": {
302
334
"type": "string",
303
335
"maxLength": 120,
304
336
"description": "Human-readable requirement."
305
337
},
306
338
"rules": {
307
339
"type": "array",
308
340
"items": {
309
341
"ref": "#rule",
310
342
"type": "ref"
311
343
},
312
344
"maxLength": 16,
313
345
"minLength": 1,
314
346
"description": "For all or any: nested rules."
315
347
},
316
348
"acquire": {
317
349
"type": "string",
318
350
"format": "uri",
319
351
"description": "URL where a player can fulfil this requirement."
320
352
},
321
353
"filters": {
322
354
"type": "array",
323
355
"items": {
324
356
"ref": "#filter",
325
357
"type": "ref"
326
358
},
327
359
"maxLength": 8,
328
360
"minLength": 1,
329
361
"description": "For matches or vouch: conditions that must hold on one record."
330
362
},
331
363
"authority": {
332
364
"type": "string",
333
365
"format": "did",
334
366
"description": "For vouch: stable DID of the record issuer."
335
367
},
336
368
"valueString": {
337
369
"type": "string",
338
370
"maxLength": 1000,
339
371
"description": "String comparison value."
340
372
},
341
373
"valueBoolean": {
342
374
"type": "boolean",
343
375
"description": "Boolean comparison value."
344
376
},
345
377
"valueInteger": {
346
378
"type": "integer",
347
379
"description": "Integer comparison value."
348
380
}
349
381
},
350
382
"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."
351
383
},
352
384
"field": {
353
385
"type": "object",
354
386
"required": [
355
387
"check",
356
388
"nsid",
357
389
"field",
358
390
"op"
359
391
],
360
392
"properties": {
361
393
"op": {
362
394
"type": "string",
363
395
"description": "How to test the field. exists/notExists check presence; eq/neq/gt/gte/lt/lte compare to a value; isViewer matches the signed-in player by their account ID (DID) and takes no value.",
364
396
"knownValues": [
365
397
"exists",
366
398
"notExists",
367
399
"isViewer",
368
400
"eq",
369
401
"neq",
370
402
"gt",
371
403
"gte",
372
404
"lt",
373
405
"lte"
374
406
]
375
407
},
376
408
"nsid": {
377
409
"type": "string",
378
410
"format": "nsid"
379
411
},
380
412
"rkey": {
381
413
"type": "string",
382
414
"maxLength": 50,
383
415
"description": "Record key. Defaults to 'self'. Use 'any' to pass if any record in the collection satisfies the condition."
384
416
},
385
417
"check": {
386
418
"type": "string",
387
419
"const": "field"
388
420
},
389
421
"field": {
390
422
"type": "string",
391
423
"maxLength": 200,
392
424
"description": "Dot-path to the property to evaluate (e.g. 'score.total', 'level'). Array traversal is not supported."
393
425
},
394
426
"label": {
395
427
"type": "string",
396
428
"maxLength": 120
397
429
},
398
430
"acquire": {
399
431
"type": "string",
400
432
"format": "uri"
401
433
},
402
434
"valueString": {
403
435
"type": "string",
404
436
"maxLength": 1000,
405
437
"description": "String comparison value. For value-taking operators, set exactly one of valueString, valueInteger, or valueBoolean."
406
438
},
407
439
"valueBoolean": {
408
440
"type": "boolean",
409
441
"description": "Boolean comparison value."
410
442
},
411
443
"valueInteger": {
412
444
"type": "integer",
413
445
"description": "Integer comparison value. Use for numeric gt/gte/lt/lte checks."
414
446
}
415
447
},
416
448
"description": "Fetches a record from the player's repository and tests one field. Covers required fields, score thresholds, flags, and enum matches."
417
449
},
418
450
"party": {
419
451
"type": "object",
420
452
"required": [
421
453
"name"
422
454
],
423
455
"properties": {
424
456
"did": {
425
457
"type": "string",
426
458
"format": "did",
427
-
"description": "AT Protocol DID of this entity's account, if they have one."
459
+
"description": "AT Protocol DID of this entity's account, if they have one. On an atmosphere.games-managed provisional definition, this is also the sole DID eligible to claim it by republishing with exact replaces[] lineage."
428
460
},
429
461
"url": {
430
462
"type": "string",
431
463
"format": "uri",
432
464
"description": "Website for this entity."
433
465
},
434
466
"name": {
435
467
"type": "string",
436
468
"maxLength": 100,
437
469
"description": "Display name of the entity."
438
470
}
439
471
},
440
472
"description": "A named entity (developer, publisher, studio) associated with the experience."
441
473
},
442
474
"video": {
443
475
"type": "object",
444
476
"required": [
445
477
"title"
446
478
],
447
479
"properties": {
448
480
"url": {
449
481
"type": "string",
450
482
"format": "uri",
451
483
"description": "Full URL to the video, used when no platform/videoId is given."
452
484
},
453
485
"title": {
454
486
"type": "string",
455
487
"maxLength": 120,
456
488
"description": "Label for the clip (e.g. 'Launch Trailer')."
457
489
},
458
490
"videoId": {
459
491
"type": "string",
460
492
"maxLength": 120,
461
493
"description": "Platform video id (e.g. a YouTube id). Combined with 'platform' to build an embed."
462
494
},
463
495
"platform": {
464
496
"type": "string",
465
497
"description": "Where the video lives. 'direct' means the url points straight at a video file or page.",
466
498
"knownValues": [
467
499
"youtube",
468
500
"vimeo",
469
501
"twitch",
470
502
"direct"
471
503
]
472
504
},
473
505
"thumbnail": {
474
506
"type": "blob",
475
507
"accept": [
476
508
"image/png",
477
509
"image/jpeg",
478
510
"image/webp"
479
511
],
480
512
"maxSize": 2000000,
481
513
"description": "Optional still frame shown before the video plays."
482
514
}
483
515
},
484
516
"description": "A trailer or gameplay clip. Provide a platform + videoId (preferred, so portals embed natively) or a direct url."
485
517
},
486
518
"vouch": {
487
519
"type": "object",
488
520
"required": [
489
521
"check",
490
522
"authority",
491
523
"nsid"
492
524
],
493
525
"properties": {
494
526
"nsid": {
495
527
"type": "string",
496
528
"format": "nsid",
497
529
"description": "The kind of record to look for in that account's repo (e.g. equipment.rpg.give)."
498
530
},
499
531
"check": {
500
532
"type": "string",
501
533
"const": "vouch"
502
534
},
503
535
"label": {
504
536
"type": "string",
505
537
"maxLength": 120
506
538
},
507
539
"acquire": {
508
540
"type": "string",
509
541
"format": "uri"
510
542
},
511
543
"filters": {
512
544
"type": "array",
513
545
"items": {
514
546
"ref": "#filter",
515
547
"type": "ref"
516
548
},
517
549
"maxLength": 8,
518
550
"description": "Conditions the record must meet, written exactly like a matches rule. Put the 'isViewer' 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."
519
551
},
520
552
"authority": {
521
553
"type": "string",
522
554
"format": "did",
523
555
"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."
524
556
}
525
557
},
526
558
"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.'"
527
559
},
528
560
"filter": {
529
561
"type": "object",
530
562
"required": [
531
563
"field",
532
564
"op"
533
565
],
534
566
"properties": {
535
567
"op": {
536
568
"type": "string",
537
569
"description": "How to test the field. exists/notExists check presence; eq/neq/gt/gte/lt/lte compare to a value; isViewer matches the signed-in player by their account ID (DID) and takes no value.",
538
570
"knownValues": [
539
571
"exists",
540
572
"notExists",
541
573
"isViewer",
542
574
"eq",
543
575
"neq",
544
576
"gt",
545
577
"gte",
546
578
"lt",
547
579
"lte"
548
580
]
549
581
},
550
582
"field": {
551
583
"type": "string",
552
584
"maxLength": 200,
553
585
"description": "Dot-path to the field."
554
586
},
555
587
"valueString": {
556
588
"type": "string",
557
589
"maxLength": 1000,
558
590
"description": "String comparison value. For value-taking operators, set exactly one typed value property."
559
591
},
560
592
"valueBoolean": {
561
593
"type": "boolean",
562
594
"description": "Boolean comparison value."
563
595
},
564
596
"valueInteger": {
565
597
"type": "integer",
566
598
"description": "Integer comparison value. Use for numeric gt/gte/lt/lte checks."
567
599
}
568
600
},
569
601
"description": "A single condition on a record's contents, used by matches and vouch. Most operators compare the field to a value. The special 'isViewer' 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."
570
602
},
571
603
"record": {
572
604
"type": "object",
573
605
"required": [
574
606
"check",
575
607
"nsid"
576
608
],
577
609
"properties": {
578
610
"nsid": {
579
611
"type": "string",
580
612
"format": "nsid",
581
613
"description": "Collection to check on the player's PDS (e.g. 'actor.rpg.sprite')."
582
614
},
583
615
"check": {
584
616
"type": "string",
585
617
"const": "record"
586
618
},
587
619
"label": {
588
620
"type": "string",
589
621
"maxLength": 120,
590
622
"description": "Human-readable requirement shown when gated (e.g. 'An rpg.actor sprite')."
591
623
},
592
624
"acquire": {
593
625
"type": "string",
594
626
"format": "uri",
595
627
"description": "URL to send players who fail, so they can fulfil the requirement."
596
628
}
597
629
},
598
630
"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?'"
599
631
},
600
632
"matches": {
601
633
"type": "object",
602
634
"required": [
603
635
"check",
604
636
"nsid",
605
637
"filters"
606
638
],
607
639
"properties": {
608
640
"nsid": {
609
641
"type": "string",
610
642
"format": "nsid"
611
643
},
612
644
"check": {
613
645
"type": "string",
614
646
"const": "matches"
615
647
},
616
648
"label": {
617
649
"type": "string",
618
650
"maxLength": 120
619
651
},
620
652
"acquire": {
621
653
"type": "string",
622
654
"format": "uri"
623
655
},
624
656
"filters": {
625
657
"type": "array",
626
658
"items": {
627
659
"ref": "#filter",
628
660
"type": "ref"
629
661
},
630
662
"maxLength": 8,
631
663
"minLength": 1,
632
664
"description": "All filters must match on the same record (AND semantics within one record)."
633
665
}
634
666
},
635
667
"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'."
636
668
},
637
669
"mediaItem": {
638
670
"type": "object",
639
671
"required": [
640
672
"image"
641
673
],
642
674
"properties": {
643
675
"alt": {
644
676
"type": "string",
645
677
"maxLength": 300,
646
678
"description": "Accessibility caption describing the image."
647
679
},
648
680
"type": {
649
681
"type": "string",
650
682
"description": "What kind of image this is, so portals can place it (e.g. 'banner' across the top, 'screenshot' in a gallery).",
651
683
"knownValues": [
652
684
"screenshot",
653
685
"banner",
654
686
"artwork",
655
687
"poster",
656
688
"background",
657
689
"logo",
658
690
"thumbnail"
659
691
]
660
692
},
661
693
"image": {
662
694
"type": "blob",
663
695
"accept": [
664
696
"image/png",
665
697
"image/jpeg",
666
698
"image/webp",
667
699
"image/gif"
668
700
],
669
701
"maxSize": 8000000,
670
702
"description": "The image blob, stored on the developer's PDS."
671
703
},
672
704
"width": {
673
705
"type": "integer",
674
706
"description": "Pixel width, if known, lets portals reserve layout space before the blob loads."
675
707
},
676
708
"height": {
677
709
"type": "integer",
678
710
"description": "Pixel height, if known."
679
711
}
680
712
},
681
713
"description": "One image in the experience's media gallery."
682
714
},
715
+
"reference": {
716
+
"type": "object",
717
+
"required": [
718
+
"role",
719
+
"nsid"
720
+
],
721
+
"properties": {
722
+
"nsid": {
723
+
"type": "string",
724
+
"format": "nsid",
725
+
"description": "Collection to read."
726
+
},
727
+
"path": {
728
+
"type": "string",
729
+
"maxLength": 200,
730
+
"description": "Optional dot-path to the relevant payload within the record, e.g. 'clunscannon' inside actor.rpg.stats/self."
731
+
},
732
+
"rkey": {
733
+
"type": "string",
734
+
"maxLength": 50,
735
+
"description": "Record key to read from a player's repo. Defaults to 'self'; use 'any' when a portal should scan the collection."
736
+
},
737
+
"role": {
738
+
"type": "string",
739
+
"description": "Why this collection is relevant. 'stats' and 'leaderboard' are for card summaries; 'activity' for player counts/recent activity; 'validation' for trusted attestations such as actor.rpg.master.",
740
+
"knownValues": [
741
+
"activity",
742
+
"stats",
743
+
"leaderboard",
744
+
"save",
745
+
"replay",
746
+
"session",
747
+
"profile",
748
+
"inventory",
749
+
"grant",
750
+
"validation",
751
+
"label",
752
+
"other"
753
+
]
754
+
},
755
+
"label": {
756
+
"type": "string",
757
+
"maxLength": 120,
758
+
"description": "Human label for this reference."
759
+
},
760
+
"verify": {
761
+
"ref": "#rule",
762
+
"type": "ref",
763
+
"description": "Optional rule that validates this reference before displaying it as trusted. Example: an actor.rpg.master vouch for a player's actor.rpg.stats system."
764
+
},
765
+
"acquire": {
766
+
"type": "string",
767
+
"format": "uri",
768
+
"description": "Optional URL where a player can create or manage this record."
769
+
},
770
+
"display": {
771
+
"type": "array",
772
+
"items": {
773
+
"ref": "#referenceDisplay",
774
+
"type": "ref"
775
+
},
776
+
"maxLength": 8,
777
+
"description": "Small metrics a portal may render on cards or profiles."
778
+
},
779
+
"filters": {
780
+
"type": "array",
781
+
"items": {
782
+
"ref": "#filter",
783
+
"type": "ref"
784
+
},
785
+
"maxLength": 8,
786
+
"description": "Optional filters used when rkey is 'any' or authority is set. Conditions must match on one record."
787
+
},
788
+
"purpose": {
789
+
"type": "string",
790
+
"maxLength": 300,
791
+
"description": "Why a portal or game reads this data."
792
+
},
793
+
"authority": {
794
+
"type": "string",
795
+
"format": "did",
796
+
"description": "When the referenced record lives in a trusted authority repo instead of the player's repo, this DID names that repo."
797
+
}
798
+
},
799
+
"description": "A non-gating record surface relevant to stats, saves, leaderboards, activity, sessions, replays, validation, labels, grants, inventory, profile enrichment, or other portal-readable context. A reference is a recipe for reading and presenting records. It is not an eligibility rule and not a promise that the game gives the player a benefit for owning that record. Put play gates in requires[] and optional gameplay integrations in includes[]."
800
+
},
683
801
"catalogRef": {
684
802
"type": "object",
685
803
"required": [
686
804
"uri"
687
805
],
688
806
"properties": {
689
807
"uri": {
690
808
"type": "string",
691
809
"format": "at-uri",
692
810
"description": "AT-URI of the external catalog record (e.g. at://did:web:gamesgamesgamesgames.games/games.gamesgamesgamesgames.game/3mgjih2bzdp2l)."
693
811
},
694
812
"name": {
695
813
"type": "string",
696
814
"maxLength": 100,
697
815
"description": "Display name of the referenced entry, so portals can show it without resolving the record."
698
816
},
699
817
"borrow": {
700
818
"type": "array",
701
819
"items": {
702
820
"type": "string",
703
821
"knownValues": [
704
822
"media",
705
823
"trailers",
706
824
"genres",
707
825
"platforms",
708
826
"releases",
709
827
"ratings",
710
828
"description",
711
829
"summary"
712
830
]
713
831
},
714
832
"maxLength": 8,
715
833
"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."
716
834
},
717
835
"source": {
718
836
"type": "string",
719
837
"maxLength": 60,
720
838
"description": "Short label for the catalog this points at (e.g. 'pentaract')."
721
839
}
722
840
},
723
841
"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."
724
842
},
725
843
"lexiconRef": {
726
844
"type": "object",
727
845
"required": [
728
846
"nsid"
729
847
],
730
848
"properties": {
731
849
"nsid": {
732
850
"type": "string",
733
851
"format": "nsid"
734
852
},
735
853
"verify": {
736
854
"ref": "#rule",
737
855
"type": "ref",
738
856
"description": "Optional rule that detects or authenticates this feature, typically a player-record match or a vouch in an issuer's separate grant collection. Portals may badge the feature when it passes. It never gates access."
739
857
},
740
858
"acquire": {
741
859
"type": "string",
742
860
"format": "uri",
743
861
"description": "Optional URL where a player can obtain this record."
744
862
},
745
863
"purpose": {
746
864
"type": "string",
747
865
"maxLength": 200,
748
-
"description": "How this lexicon enriches the experience when present. Shown to players."
866
+
"description": "The player-visible benefit this record unlocks or enriches when present. Shown to players."
867
+
}
868
+
},
869
+
"description": "An optional player-facing record collection this game can use when present. This is about gameplay behavior or player benefit, not portal analytics. Example: an rpg.actor sprite can appear in the game, or an equipment item can unlock a bonus. 'verify' may inspect the player's record or an issuer-controlled grant collection so portals can badge whether the benefit is available."
870
+
},
871
+
"referenceDisplay": {
872
+
"type": "object",
873
+
"required": [
874
+
"key",
875
+
"label",
876
+
"path"
877
+
],
878
+
"properties": {
879
+
"key": {
880
+
"type": "string",
881
+
"maxLength": 80,
882
+
"description": "Stable metric key within this definition, e.g. 'bestDistance'."
883
+
},
884
+
"path": {
885
+
"type": "string",
886
+
"maxLength": 200,
887
+
"description": "Dot-path to the value, relative to the reference path when set."
888
+
},
889
+
"unit": {
890
+
"type": "string",
891
+
"maxLength": 20,
892
+
"description": "Unit suffix such as 'm', 'XP', or 'pts'."
893
+
},
894
+
"label": {
895
+
"type": "string",
896
+
"maxLength": 80,
897
+
"description": "Short label shown before or beside the value."
898
+
},
899
+
"format": {
900
+
"type": "string",
901
+
"description": "Presentation hint for the value.",
902
+
"knownValues": [
903
+
"string",
904
+
"integer",
905
+
"number",
906
+
"distance",
907
+
"percent",
908
+
"duration",
909
+
"datetime",
910
+
"boolean"
911
+
]
912
+
},
913
+
"divideBy": {
914
+
"type": "integer",
915
+
"minimum": 1,
916
+
"description": "Integer divisor applied before display, e.g. divideBy 10 for decimeters to meters."
917
+
},
918
+
"priority": {
919
+
"type": "integer",
920
+
"description": "Lower numbers display first."
921
+
},
922
+
"aggregate": {
923
+
"type": "string",
924
+
"description": "How to reduce multiple records. Defaults to 'first' for fixed rkey records and 'count' for collection scans.",
925
+
"knownValues": [
926
+
"first",
927
+
"count",
928
+
"max",
929
+
"min",
930
+
"sum",
931
+
"latest",
932
+
"exists"
933
+
]
934
+
},
935
+
"multiplyBy": {
936
+
"type": "integer",
937
+
"description": "Integer multiplier applied before display. Use with divideBy for rational scaling."
938
+
},
939
+
"maximumFractionDigits": {
940
+
"type": "integer",
941
+
"maximum": 6,
942
+
"minimum": 0,
943
+
"description": "Decimal precision for numeric display."
749
944
}
750
945
},
751
-
"description": "A record collection this game can use when present, never a requirement. 'nsid' names the optional player-facing record; 'verify' may inspect that record or a separate issuer-controlled grant collection."
946
+
"description": "A declarative display metric derived from a referenced record or collection. Portals may ignore unsupported formats or aggregates."
752
947
}
753
948
},
754
949
"$type": "com.atproto.lexicon.schema",
755
950
"lexicon": 1,
756
951
"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."
757
952
}