{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://blur.entropylan.party/live-timing/feed.schema.json",
  "title": "Blur game-state feed message",
  "description": "One websocket text frame of the game-state feed (ADR 0019, docs/state-websocket.md). Unknown keys, events and fields are allowed: clients ignore what they do not know.",
  "oneOf": [
    { "$ref": "#/$defs/snapshot" },
    { "$ref": "#/$defs/change" },
    { "$ref": "#/$defs/event" }
  ],
  "$defs": {
    "ms": { "type": "integer", "minimum": 0 },
    "racer": { "type": "integer", "minimum": 0 },
    "powerup": { "type": "string", "pattern": "^[a-z_]+$", "description": "nitro, shock, shunt, bolt, barge, mine, repair, shield, or unknown" },
    "envelope": {
      "properties": {
        "race_id": { "type": "string", "description": "UUIDv7 minted by the host at race start; present only during a race" },
        "race_ms": { "$ref": "#/$defs/ms" }
      }
    },
    "snapshot": {
      "allOf": [ { "$ref": "#/$defs/envelope" } ],
      "type": "object",
      "required": ["type", "state"],
      "properties": {
        "type": { "const": "snapshot" },
        "state": {
          "type": "object",
          "properties": {
            "screen": { "type": "string" },
            "mode": { "type": "string" },
            "race_type": { "type": "string" },
            "track": { "type": "string" },
            "track_name": { "type": "string" },
            "route_name": { "type": "string" },
            "lobby": { "enum": ["none", "hosting", "joined"] },
            "players": { "type": "integer", "minimum": 0 },
            "racer": { "$ref": "#/$defs/racer" },
            "roster": { "$ref": "#/$defs/roster" }
          }
        }
      }
    },
    "change": {
      "allOf": [
        { "$ref": "#/$defs/envelope" },
        {
          "description": "Typed values for the known keys; null clears a key. Unknown keys are unconstrained.",
          "allOf": [
            { "if": { "properties": { "key": { "enum": ["screen", "mode", "race_type", "track", "track_name", "route_name"] } } }, "then": { "properties": { "value": { "type": ["string", "null"] } } } },
            { "if": { "properties": { "key": { "const": "lobby" } } }, "then": { "properties": { "value": { "enum": ["none", "hosting", "joined", null] } } } },
            { "if": { "properties": { "key": { "enum": ["players", "racer", "lap", "laps", "position", "racers"] } } }, "then": { "properties": { "value": { "type": ["integer", "null"], "minimum": 0 } } } },
            { "if": { "properties": { "key": { "const": "roster" } } }, "then": { "properties": { "value": { "oneOf": [ { "$ref": "#/$defs/roster" }, { "type": "null" } ] } } } },
            { "if": { "properties": { "key": { "const": "standings" } } }, "then": { "properties": { "value": { "oneOf": [ { "$ref": "#/$defs/standings" }, { "type": "null" } ] } } } },
            { "if": { "properties": { "key": { "const": "race_result" } } }, "then": { "properties": { "value": { "oneOf": [ { "$ref": "#/$defs/race_result" }, { "type": "null" } ] } } } }
          ]
        }
      ],
      "type": "object",
      "required": ["type", "key", "value"],
      "properties": {
        "type": { "const": "change" },
        "key": { "type": "string" }
      }
    },
    "roster": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "racer": { "$ref": "#/$defs/racer" },
          "join_ref": { "type": "integer", "minimum": 0 },
          "name": { "type": "string" },
          "car": { "type": "string" },
          "mods": { "type": "array", "items": { "type": "string" } },
          "local": { "type": "boolean" },
          "ai": { "type": "boolean" }
        }
      }
    },
    "kindCounts": {
      "type": "object",
      "additionalProperties": { "type": "integer", "minimum": 0 }
    },
    "totals": {
      "type": "object",
      "properties": {
        "uses": { "$ref": "#/$defs/kindCounts" },
        "collections": { "$ref": "#/$defs/kindCounts" },
        "hits_dealt": { "$ref": "#/$defs/kindCounts" },
        "hits_taken": { "$ref": "#/$defs/kindCounts" },
        "hits_shielded": { "$ref": "#/$defs/kindCounts" },
        "damage_dealt": { "$ref": "#/$defs/kindCounts" },
        "damage_taken": { "$ref": "#/$defs/kindCounts" },
        "wrecks_dealt": { "type": "integer", "minimum": 0 },
        "wrecks_suffered": { "type": "integer", "minimum": 0 },
        "self_hits": { "type": "integer", "minimum": 0 },
        "decoys_dropped": { "type": "integer", "minimum": 0 },
        "decoy_hits_dealt": { "type": "integer", "minimum": 0 },
        "decoy_hits_taken": { "type": "integer", "minimum": 0 }
      }
    },
    "standing": {
      "type": "object",
      "required": ["racer", "position", "lap", "finished"],
      "properties": {
        "racer": { "$ref": "#/$defs/racer" },
        "position": { "type": "integer", "minimum": 1 },
        "lap": { "type": "integer", "minimum": 0 },
        "finished": { "type": "boolean" },
        "mph": { "type": "number" },
        "lap_ms": { "$ref": "#/$defs/ms" },
        "race_ms": { "$ref": "#/$defs/ms" },
        "time_ms": { "$ref": "#/$defs/ms" },
        "last_ms": { "$ref": "#/$defs/ms" },
        "best_ms": { "$ref": "#/$defs/ms" },
        "dist": { "type": "number" },
        "gap_ms": { "$ref": "#/$defs/ms" },
        "int_ms": { "$ref": "#/$defs/ms" },
        "powerups": {
          "type": "array",
          "minItems": 3,
          "maxItems": 3,
          "items": { "oneOf": [ { "$ref": "#/$defs/powerup" }, { "type": "null" } ] }
        },
        "award_stats": { "type": "object", "additionalProperties": { "type": "number" } },
        "totals": { "$ref": "#/$defs/totals" }
      }
    },
    "standings": { "type": "array", "items": { "$ref": "#/$defs/standing" } },
    "award": {
      "type": "object",
      "required": ["award", "racer"],
      "properties": {
        "award": { "type": "string" },
        "racer": { "$ref": "#/$defs/racer" },
        "stat": { "type": "string" },
        "value": { "type": "number" },
        "unit": { "type": "string" },
        "display": { "type": "string" }
      }
    },
    "race_result": {
      "type": "object",
      "required": ["final", "completed", "standings"],
      "properties": {
        "final": { "type": "boolean" },
        "completed": { "type": "boolean" },
        "standings": { "$ref": "#/$defs/standings" },
        "awards": { "type": "array", "items": { "$ref": "#/$defs/award" } }
      }
    },
    "event": {
      "allOf": [
        { "$ref": "#/$defs/envelope" },
        {
          "description": "Payloads for the known events. Unknown events are unconstrained.",
          "allOf": [
            { "if": { "properties": { "event": { "const": "hit" } } }, "then": { "properties": { "data": { "$ref": "#/$defs/hit" } } } },
            { "if": { "properties": { "event": { "const": "use" } } }, "then": { "properties": { "data": { "$ref": "#/$defs/use" } } } },
            { "if": { "properties": { "event": { "const": "collect" } } }, "then": { "properties": { "data": { "$ref": "#/$defs/collect" } } } },
            { "if": { "properties": { "event": { "const": "decoy_drop" } } }, "then": { "properties": { "data": { "$ref": "#/$defs/decoy_drop" } } } },
            { "if": { "properties": { "event": { "const": "decoy_hit" } } }, "then": { "properties": { "data": { "$ref": "#/$defs/decoy_hit" } } } }
          ]
        }
      ],
      "type": "object",
      "required": ["type", "event", "data"],
      "properties": {
        "type": { "const": "event" },
        "event": { "type": "string" },
        "data": { "type": "object" }
      }
    },
    "hit": {
      "type": "object",
      "required": ["victim", "powerup", "damage", "shielded", "wrecked", "serial"],
      "properties": {
        "victim": { "$ref": "#/$defs/racer" },
        "attacker": { "$ref": "#/$defs/racer" },
        "powerup": { "$ref": "#/$defs/powerup" },
        "damage": { "type": "integer", "minimum": 0 },
        "shielded": { "type": "boolean" },
        "wrecked": { "type": "boolean" },
        "serial": { "type": "integer", "minimum": 0 }
      }
    },
    "use": {
      "type": "object",
      "required": ["racer", "powerup", "serial"],
      "properties": {
        "racer": { "$ref": "#/$defs/racer" },
        "powerup": { "$ref": "#/$defs/powerup" },
        "serial": { "type": "integer", "minimum": 0 }
      }
    },
    "collect": {
      "type": "object",
      "required": ["racer", "powerup"],
      "properties": {
        "racer": { "$ref": "#/$defs/racer" },
        "powerup": { "$ref": "#/$defs/powerup" },
        "inferred": { "const": true }
      }
    },
    "decoy_drop": {
      "type": "object",
      "required": ["racer", "powerup", "serial"],
      "properties": {
        "racer": { "$ref": "#/$defs/racer" },
        "powerup": { "$ref": "#/$defs/powerup" },
        "serial": { "type": "integer", "minimum": 0 }
      }
    },
    "decoy_hit": {
      "type": "object",
      "required": ["victim", "powerup", "serial"],
      "properties": {
        "victim": { "$ref": "#/$defs/racer" },
        "attacker": { "$ref": "#/$defs/racer" },
        "powerup": { "$ref": "#/$defs/powerup" },
        "serial": { "type": "integer", "minimum": 0 }
      }
    }
  }
}
