diff --git a/schema/sprite-catalog.schema.json b/schema/sprite-catalog.schema.json new file mode 100644 index 0000000..8466153 --- /dev/null +++ b/schema/sprite-catalog.schema.json @@ -0,0 +1,108 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.thewichersfamily.com/thethreemagi/sprite-catalog/raw/branch/main/schema/sprite-catalog.schema.json", + "title": "Sprite catalog season file", + "description": "One file per season. Variants are nested under their base sprite to avoid repeating base_name and row_name; the bot flattens them to one row per variant on load.", + "type": "object", + "additionalProperties": false, + "required": ["season", "game_mode", "sprites"], + "properties": { + "season": { + "type": "string", + "pattern": "^c[0-9]+s[0-9]+$", + "description": "Season identifier, e.g. c7s3. Must match the filename stem." + }, + "game_mode": { + "$ref": "#/$defs/gameMode", + "description": "Default game mode for every sprite in this file. Individual sprites may override it." + }, + "cross_mode_trading": { + "type": "boolean", + "default": false, + "description": "Whether sprites from different game modes may be matched against each other. Defaults to false; leave it false unless the policy is deliberately changed." + }, + "notes": { + "type": "string", + "description": "Free text. JSON has no comments, so anything you would have written as one goes here." + }, + "sprites": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/sprite" } + } + }, + "$defs": { + "gameMode": { + "type": "string", + "enum": ["br", "og", "reload", "festival", "rocket_racing", "lego"], + "description": "Extend this list rather than inventing values; the bot validates against it." + }, + "status": { + "type": "string", + "enum": ["unreleased", "released", "hidden"], + "description": "unreleased: known but not yet obtainable. released: obtainable and tradeable. hidden: dropped out of matching but retained in inventories, and reversible — most hidden sprites come back." + }, + "sprite": { + "type": "object", + "additionalProperties": false, + "required": ["base_name", "row_name", "variants"], + "properties": { + "base_name": { + "type": "string", + "minLength": 1, + "description": "The sprite's base name, shared across its variants. Unique within a season." + }, + "row_name": { + "type": "string", + "minLength": 1, + "description": "The collection row this sprite belongs to. Autocomplete matches on this as well as base_name." + }, + "game_mode": { + "$ref": "#/$defs/gameMode", + "description": "Overrides the season-level game_mode for this sprite only." + }, + "notes": { "type": "string" }, + "variants": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/variant" } + } + } + }, + "variant": { + "type": "object", + "additionalProperties": false, + "required": ["variant", "status"], + "properties": { + "variant": { + "type": "string", + "minLength": 1, + "description": "Variant name, unique within its base sprite. Use \"Default\" when a sprite has only one form." + }, + "status": { "$ref": "#/$defs/status" }, + "released_at": { + "type": "string", + "format": "date", + "description": "ISO 8601 date. Required in practice once status is released; omit while unreleased." + }, + "max_stack": { + "type": "integer", + "minimum": 1, + "default": 1, + "description": "Maximum copies a member can hold. Anything above 1 can enter the give pool as a spare." + }, + "rarity_weight": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Retained for display and analytics only. The matcher does NOT use it — rarity matching was removed deliberately, because value-matching is how selfish players win and the pool starves." + }, + "confirmed": { + "type": "boolean", + "default": false, + "description": "Flips true the first time a real member is recorded owning this variant. Unconfirmed entries are deprioritised in autocomplete and excluded from the coverage metric, since variant lists are assumed-complete guesses until proven." + }, + "notes": { "type": "string" } + } + } + } +}