{
"id": "games.gamesgamesgamesgames.achievement",
"defs": {
"main": {
"key": "any",
"type": "record",
"record": {
"type": "object",
"required": [
"id",
"criteria"
],
"properties": {
"id": {
"type": "string",
"maxLength": 64,
"description": "Matches the rkey, and matches BOTH the rkey and `name` of the `community.lexicon.badge.definition` this awards. That shared id is the link between the two records — deliberately, instead of a strong ref: a ref needs the definition's CID, which does not exist until the definition is published, so a static file could never carry a correct one. The job resolves uri and cid at award time, which also means re-publishing a definition cannot leave a stale CID behind in a second record."
},
"rank": {
"type": "integer",
"description": "Display order. Same convention as `pet.trezy.group` — banded, so a family can be inserted without renumbering its neighbours."
},
"hidden": {
"type": "boolean",
"description": "ABSENT MEANS SHOWN. A hidden achievement is not listed until it is earned. Reserved for ones whose statement would be a spoiler; a reader writing `if hidden then` hides nothing by default, which is the safe direction."
},
"criteria": {
"ref": "#criteria",
"type": "ref",
"description": "What has to be true. Evaluated server-side by the achievements job against the ledger, never by a client — a client that could decide it had earned something would be a client that could mint one."
}
}
},
"description": "How a badge is earned. Published to the game's repo, one per achievement, alongside the `community.lexicon.badge.definition` it points at.\n\nThe split is deliberate: the DEFINITION is the portable, standard artefact any client can read and render, and this is the game's private bookkeeping. Nothing here is needed to display a trophy — only to decide whether somebody has earned one — so it stays out of the interoperable record rather than bloating it with rules no other reader could evaluate anyway.\n\nThe rkey is the achievement id."
},
"criteria": {
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"maxLength": 32,
"description": "NOT an enum, for the reason `tier` and `behavior.type` are not: a new kind should be publishable. A job that meets a kind it does not implement must skip that achievement and keep evaluating the rest, never refuse the whole run.",
"knownValues": [
"first-pet",
"first-purchase",
"stage-full",
"distinct-species",
"group-complete",
"tier-complete",
"activity-complete",
"coins-spent",
"coins-collected",
"pet-level",
"pet-level-breadth"
]
},
"tier": {
"type": "string",
"maxLength": 64,
"description": "For `tier-complete`: the `pet.trezy.tier` id."
},
"count": {
"type": "integer",
"minimum": 1,
"description": "For the counting kinds: how many distinct species, how many coins, what level, how many species at that level."
},
"group": {
"type": "string",
"maxLength": 64,
"description": "For `group-complete`: the `pet.trezy.group` id whose every species must be owned."
}
},
"description": "The params a kind needs differ by kind, which lexicon cannot express as a discriminated union — the same compromise `pet.trezy.species#behavior` makes. Every param is optional here and the job validates the combination each kind requires."
}
},
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}