{
  "$ref": "#/definitions/match-stats",
  "definitions": {
    "match-stats": {
      "type": "object",
      "properties": {
        "stats_version": {
          "type": "integer",
          "description": "bumped whenever a field changes meaning"
        },
        "map": {
          "type": "string"
        },
        "seed": {
          "type": "integer"
        },
        "ticks": {
          "type": "integer",
          "description": "ticks resolved"
        },
        "winner": {
          "anyOf": [
            {
              "$ref": "#/definitions/match-stats/properties/seed"
            },
            {
              "type": "null"
            }
          ],
          "description": "fighter id of the winner, or null for a draw"
        },
        "reason": {
          "type": [
            "string",
            "null"
          ],
          "description": "how the match ended"
        },
        "fighters": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "description": "fighter id, as in hello.bot_id"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              },
              "hp": {
                "type": "integer",
                "description": "hp at the end of the match"
              },
              "alive": {
                "type": "boolean"
              },
              "moves": {
                "type": "integer",
                "description": "one-tile steps taken"
              },
              "moves_blocked": {
                "type": "integer",
                "description": "accepted steps that didn't happen because another fighter was in the way"
              },
              "dashes": {
                "$ref": "#/definitions/match-stats/properties/seed"
              },
              "dash_tiles": {
                "type": "integer",
                "description": "tiles covered by dashing"
              },
              "weapons": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "uses": {
                      "type": "integer",
                      "description": "attacks fired: sent and accepted by the engine"
                    },
                    "hits": {
                      "type": "integer",
                      "description": "hits on an opponent"
                    },
                    "damage": {
                      "type": "integer",
                      "description": "hp those hits removed, the power buff included"
                    }
                  },
                  "required": [
                    "uses",
                    "hits",
                    "damage"
                  ],
                  "additionalProperties": false
                },
                "description": "by attack name; every attack in the rules is listed. Accuracy is hits / uses"
              },
              "ricochets": {
                "type": "object",
                "properties": {
                  "bounces": {
                    "type": "integer",
                    "description": "shots that bounced off a wall"
                  },
                  "hits": {
                    "type": "integer",
                    "description": "hits scored after a bounce (also counted in weapons)"
                  },
                  "damage": {
                    "$ref": "#/definitions/match-stats/properties/seed"
                  }
                },
                "required": [
                  "bounces",
                  "hits",
                  "damage"
                ],
                "additionalProperties": false
              },
              "pickups": {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/definitions/match-stats/properties/seed"
                },
                "description": "pickups taken, by kind; every kind in the rules is listed"
              },
              "healed": {
                "type": "integer",
                "description": "hp restored by health pickups"
              },
              "damage_dealt": {
                "type": "integer",
                "description": "hp removed from opponents"
              },
              "dealt_by_source": {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/definitions/match-stats/properties/seed"
                },
                "description": "damage_dealt by source: attack names, \"wall_slam\", \"collision\""
              },
              "damage_taken": {
                "type": "integer",
                "description": "hp lost"
              },
              "taken_by_source": {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/definitions/match-stats/properties/seed"
                },
                "description": "damage_taken by source: attack names, \"wall_slam\", \"collision\", \"zone\""
              },
              "wall_slams": {
                "type": "integer",
                "description": "opponents this fighter's repulsor slammed into a wall"
              },
              "knocked_back": {
                "type": "integer",
                "description": "times this fighter was shoved"
              },
              "stunned": {
                "type": "integer",
                "description": "times this fighter was stunned"
              },
              "zone_ticks": {
                "type": "integer",
                "description": "ticks ended outside the safe zone"
              },
              "rejected": {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/definitions/match-stats/properties/seed"
                },
                "description": "parts of turns the engine refused, by reason"
              },
              "refused_turns": {
                "type": "integer",
                "description": "turns with at least one refused part"
              },
              "refused_turns_by_reason": {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/definitions/match-stats/properties/seed"
                },
                "description": "turns with a refused part, by reason; a turn counts once per reason"
              },
              "server_idles": {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/definitions/match-stats/properties/seed"
                },
                "description": "ticks the server played as idle for this fighter, by reason (timeout, malformed_json, disconnected…)"
              },
              "timing": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "replies": {
                        "type": "integer",
                        "description": "timed replies, timeouts included (a reply that never came counts at the deadline)"
                      },
                      "mean_ms": {
                        "$ref": "#/definitions/match-stats/properties/seed"
                      },
                      "p50_ms": {
                        "type": "integer",
                        "description": "median reply; added up over several matches, the upper bound of its histogram bucket"
                      },
                      "p95_ms": {
                        "type": "integer",
                        "description": "95th percentile reply; added up over several matches, the upper bound of its histogram bucket"
                      },
                      "max_ms": {
                        "type": "integer",
                        "description": "slowest reply"
                      },
                      "near_limit": {
                        "type": "integer",
                        "description": "replies that took at least 80% of the tick limit: one slow moment from a timeout"
                      },
                      "tick_ms": {
                        "type": "integer",
                        "description": "the per-tick limit the replies were timed against"
                      },
                      "budget_ms": {
                        "type": "integer",
                        "description": "the match's total response-time budget"
                      },
                      "budget_ms_left": {
                        "type": "integer",
                        "description": "budget left at the end of the match (the lowest, added up over several)"
                      },
                      "histogram": {
                        "type": "array",
                        "items": {
                          "$ref": "#/definitions/match-stats/properties/seed"
                        },
                        "description": "replies per bucket: upper bounds 10, 25, 50, 100, 200, 300, 400, 500, 600, 700, 800, 850, 900, 950, 1000 thousandths of tick_ms, then slower"
                      }
                    },
                    "required": [
                      "replies",
                      "mean_ms",
                      "p50_ms",
                      "p95_ms",
                      "max_ms",
                      "near_limit",
                      "tick_ms",
                      "budget_ms",
                      "budget_ms_left",
                      "histogram"
                    ],
                    "additionalProperties": false,
                    "description": "how long the bot took to reply, as the server timed it: from sending the observation to the reply arriving, network included"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "reply times; null when nothing was timed (the simulator doesn't time replies)"
              },
              "disconnects": {
                "type": "integer",
                "description": "contiguous runs of recorded disconnected idle turns for this fighter, not socket-close events"
              },
              "disqualified": {
                "type": "boolean",
                "description": "the match ended because this fighter failed too many ticks in a row"
              }
            },
            "required": [
              "id",
              "name",
              "version",
              "hp",
              "alive",
              "moves",
              "moves_blocked",
              "dashes",
              "dash_tiles",
              "weapons",
              "ricochets",
              "pickups",
              "healed",
              "damage_dealt",
              "dealt_by_source",
              "damage_taken",
              "taken_by_source",
              "wall_slams",
              "knocked_back",
              "stunned",
              "zone_ticks",
              "rejected",
              "refused_turns",
              "refused_turns_by_reason",
              "server_idles",
              "timing",
              "disconnects",
              "disqualified"
            ],
            "additionalProperties": false
          }
        }
      },
      "required": [
        "stats_version",
        "map",
        "seed",
        "ticks",
        "winner",
        "reason",
        "fighters"
      ],
      "additionalProperties": false,
      "description": "what each fighter did over one match"
    }
  },
  "$schema": "http://json-schema.org/draft-07/schema#"
}
