{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.ralyoo.com/schema/ralyoo.json",
  "title": "Ralyoo game manifest",
  "type": "object",
  "required": [
    "id",
    "version",
    "sdk",
    "title",
    "description",
    "author",
    "players",
    "pillars",
    "locales",
    "media"
  ],
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Schema URL, for editor completion."
    },
    "id": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
      "minLength": 3,
      "maxLength": 40,
      "description": "Permanent identifier: lowercase letters, digits, and dashes. Part of the bundle URL and of the storage key, so it never changes after the first upload."
    },
    "version": {
      "type": "string",
      "pattern": "^\\d+\\.\\d+\\.\\d+(-[0-9A-Za-z.-]+)?$",
      "description": "Version of the game (semver). Every upload needs a new one; a published version is immutable."
    },
    "sdk": {
      "type": "string",
      "minLength": 1,
      "maxLength": 40,
      "description": "SDK versions the game runs on, as a semver range, e.g. \">=1.1 <2\". Checked against the platform at upload and before the game is mounted."
    },
    "title": {
      "description": "Name shown in the catalog, the countdown, and the menu. One line in the lobby.",
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 22
        },
        {
          "type": "object",
          "minProperties": 1,
          "propertyNames": {
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "additionalProperties": {
            "type": "string",
            "minLength": 1,
            "maxLength": 22
          }
        }
      ]
    },
    "description": {
      "description": "One or two short sentences under the title: two lines in the lobby.",
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        {
          "type": "object",
          "minProperties": 1,
          "propertyNames": {
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "additionalProperties": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          }
        }
      ]
    },
    "controls": {
      "description": "How it is played, in a few words, e.g. \"Joystick, dorong, nempel\".",
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 60
        },
        {
          "type": "object",
          "minProperties": 1,
          "propertyNames": {
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "additionalProperties": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60
          }
        }
      ]
    },
    "badge": {
      "description": "Optional short tag on the catalog card, e.g. \"Demo SDK\".",
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 24
        },
        {
          "type": "object",
          "minProperties": 1,
          "propertyNames": {
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "additionalProperties": {
            "type": "string",
            "minLength": 1,
            "maxLength": 24
          }
        }
      ]
    },
    "author": {
      "type": "object",
      "required": [
        "name"
      ],
      "description": "Who made the game.",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 60
        },
        "url": {
          "type": "string",
          "pattern": "^https://",
          "maxLength": 200
        }
      },
      "additionalProperties": false
    },
    "players": {
      "type": "object",
      "required": [
        "min",
        "max"
      ],
      "description": "How many players the game supports (the platform seats 1–4).",
      "properties": {
        "min": {
          "type": "integer",
          "minimum": 1,
          "maximum": 4
        },
        "max": {
          "type": "integer",
          "minimum": 1,
          "maximum": 4
        }
      },
      "additionalProperties": false
    },
    "pillars": {
      "type": "array",
      "minItems": 1,
      "uniqueItems": true,
      "items": {
        "enum": [
          "konsol",
          "rahasia"
        ]
      },
      "description": "Which Ralyoo pillar the game belongs to: \"konsol\" (the phone is a gamepad) or \"rahasia\" (the phone shows secret information)."
    },
    "locales": {
      "type": "array",
      "minItems": 1,
      "uniqueItems": true,
      "items": {
        "type": "string",
        "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
      },
      "description": "Languages the game ships, default first. Localized manifest texts must cover the default."
    },
    "media": {
      "type": "object",
      "required": [
        "icon",
        "cover",
        "color"
      ],
      "description": "Catalog artwork: paths inside the bundle, and the game colour.",
      "properties": {
        "icon": {
          "type": "string",
          "pattern": "^(?!.*\\.\\.)[A-Za-z0-9_-][A-Za-z0-9_./-]*\\.(svg|png)$",
          "maxLength": 120,
          "description": "Square icon used as a mask (one colour, transparent background). SVG preferred."
        },
        "cover": {
          "type": "string",
          "pattern": "^(?!.*\\.\\.)[A-Za-z0-9_-][A-Za-z0-9_./-]*\\.(png|webp|svg|jpg|jpeg|avif)$",
          "maxLength": 120,
          "description": "Cover image, 16:9, at least 1280×720."
        },
        "color": {
          "type": "string",
          "pattern": "^#[0-9A-Fa-f]{6}$",
          "description": "Game colour as #rrggbb; must read on the dark host background (≥ 3:1)."
        }
      },
      "additionalProperties": false
    },
    "tier": {
      "enum": [
        "free"
      ],
      "description": "Access tier. Only \"free\" for now."
    },
    "entry": {
      "type": "object",
      "required": [
        "host",
        "controller"
      ],
      "description": "Entry points. The host entry is an ES module; controllers are declarative.",
      "properties": {
        "host": {
          "const": "main.js"
        },
        "controller": {
          "const": "declarative"
        }
      },
      "additionalProperties": false
    },
    "inputs": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "enum": [
          "buttons",
          "dpad",
          "joystick",
          "cards",
          "vote"
        ]
      },
      "description": "Controller widgets the game uses (shown to players before they pick it)."
    },
    "permissions": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "enum": [
          "vibrate"
        ]
      },
      "description": "Device capabilities the game asks for."
    },
    "offline": {
      "type": "boolean",
      "description": "True when the game needs nothing but its own bundle (always, for now)."
    },
    "opening": {
      "enum": [
        "platform",
        "game"
      ],
      "description": "\"platform\" (default): the platform shows \"3·2·1·GAS!\". \"game\": the game runs its own opening."
    },
    "theme": {
      "type": "object",
      "description": "Colours, frame, and labels of the host menu while the game is on screen. Colours below the contrast floor are ignored as a set.",
      "properties": {
        "panel": {
          "type": "string",
          "pattern": "^#[0-9A-Fa-f]{6}$"
        },
        "accent": {
          "type": "string",
          "pattern": "^#[0-9A-Fa-f]{6}$"
        },
        "text": {
          "type": "string",
          "pattern": "^#[0-9A-Fa-f]{6}$"
        },
        "frame": {
          "type": "string",
          "pattern": "^(?!.*\\.\\.)[A-Za-z0-9_-][A-Za-z0-9_./-]*\\.(png|webp|svg)$",
          "maxLength": 120
        },
        "labels": {
          "type": "object",
          "additionalProperties": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 80
            }
          }
        }
      },
      "additionalProperties": false
    }
  },
  "additionalProperties": true
}
