{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://save-one-stroke.invalid/level-v1.schema.json",
  "title": "救一笔！单关文件 v1",
  "description": "纯数据，不执行脚本。UTF-8 单文件最多 65536 字节；Schema 合法不等于可解。跨字段关系还须运行 js/level-format.js 或 npm run level:validate。",
  "$comment": "附加运行时约束：gold<=ink；矩形 x+w<=720、y+h<=600；gap[0]<gap[1]；force.start<force.end。内置 manifest 要求 id 从1连续；外部单关无此顺序要求。",
  "type": "object",
  "additionalProperties": false,
  "required": ["format","version","id","chapter","title","tag","brief","condition","quip","success","failure","ink","gold","chair","terrain","load","hint"],
  "properties": {
    "format": {"const":"save-one-stroke-level"},
    "version": {"const":1},
    "id": {"type":"integer","minimum":1,"maximum":999999},
    "chapter": {"enum":["入门","挑战"]},
    "title": {"type":"string","minLength":1,"maxLength":40,"pattern":"\\S"},
    "tag": {"type":"string","minLength":1,"maxLength":80,"pattern":"\\S"},
    "brief": {"type":"string","minLength":1,"maxLength":240,"pattern":"\\S"},
    "condition": {"type":"string","minLength":1,"maxLength":300,"pattern":"\\S"},
    "quip": {"type":"string","minLength":1,"maxLength":120,"pattern":"\\S"},
    "success": {"type":"string","minLength":1,"maxLength":240,"pattern":"\\S"},
    "failure": {"type":"string","minLength":1,"maxLength":240,"pattern":"\\S"},
    "ink": {"type":"number","minimum":10,"maximum":2000},
    "gold": {"type":"number","minimum":10,"maximum":2000},
    "chair": {"type":"array","minItems":1,"maxItems":30,"prefixItems":[{"allOf":[{"$ref":"#/$defs/woodRect"},{"properties":{"kind":{"const":"seat"}}}]}],"items":{"$ref":"#/$defs/woodRect"}},
    "terrain": {"type":"array","minItems":1,"maxItems":30,"items":{"$ref":"#/$defs/terrainRect"}},
    "load": {"$ref":"#/$defs/load"},
    "hint": {"type":"array","minItems":1,"maxItems":4,"items":{"type":"string","minLength":1,"maxLength":300,"pattern":"\\S"}},
    "forbidden": {"$ref":"#/$defs/rect"},
    "forbiddenZones": {"type":"array","minItems":1,"maxItems":10,"items":{"$ref":"#/$defs/zoneRect"}},
    "gap": {"type":"array","minItems":2,"maxItems":2,"items":{"type":"number","minimum":0,"maximum":720}},
    "labels": {"type":"array","minItems":0,"maxItems":12,"items":{"$ref":"#/$defs/label"}},
    "forces": {"type":"array","minItems":0,"maxItems":10,"items":{"$ref":"#/$defs/force"}}
  },
  "not": {"required":["forbidden","forbiddenZones"]},
  "$defs": {
    "x": {"type":"number","minimum":0,"maximum":719},
    "y": {"type":"number","minimum":0,"maximum":480},
    "w": {"type":"number","minimum":1,"maximum":720},
    "h": {"type":"number","minimum":1,"maximum":600},
    "rect": {
      "type":"object","additionalProperties":false,"required":["x","y","w","h"],
      "properties":{"x":{"$ref":"#/$defs/x"},"y":{"$ref":"#/$defs/y"},"w":{"$ref":"#/$defs/w"},"h":{"$ref":"#/$defs/h"}}
    },
    "woodRect": {
      "type":"object","additionalProperties":false,"required":["x","y","w","h","kind"],
      "properties":{"x":{"$ref":"#/$defs/x"},"y":{"$ref":"#/$defs/y"},"w":{"$ref":"#/$defs/w"},"h":{"$ref":"#/$defs/h"},"kind":{"enum":["seat","leg"]}}
    },
    "terrainRect": {
      "type":"object","additionalProperties":false,"required":["x","y","w","h"],
      "properties":{"x":{"$ref":"#/$defs/x"},"y":{"$ref":"#/$defs/y"},"w":{"$ref":"#/$defs/w"},"h":{"$ref":"#/$defs/h"},"step":{"type":"boolean"}}
    },
    "zoneRect": {
      "type":"object","additionalProperties":false,"required":["x","y","w","h"],
      "properties":{"x":{"$ref":"#/$defs/x"},"y":{"$ref":"#/$defs/y"},"w":{"$ref":"#/$defs/w"},"h":{"$ref":"#/$defs/h"},"label":{"type":"string","minLength":1,"maxLength":40,"pattern":"\\S"}}
    },
    "point": {
      "type":"object","additionalProperties":false,"required":["x","y"],
      "properties":{"x":{"type":"number","minimum":0,"maximum":720},"y":{"$ref":"#/$defs/y"}}
    },
    "load": {
      "type":"object","additionalProperties":false,"required":["x","mass"],
      "properties":{
        "x":{"type":"number","minimum":25,"maximum":695},
        "mass":{"type":"number","minimum":0.1,"maximum":200},
        "cat":{"type":"boolean"},
        "catX":{"type":"number","minimum":25,"maximum":695},
        "catMass":{"type":"number","minimum":0.1,"maximum":200}
      },
      "if":{"required":["cat"],"properties":{"cat":{"const":true}}},
      "then":{"required":["catX","catMass"]}
    },
    "label": {
      "type":"object","additionalProperties":false,"required":["x","y","text","to"],
      "properties":{
        "x":{"type":"number","minimum":0,"maximum":720},"y":{"$ref":"#/$defs/y"},
        "text":{"type":"string","minLength":1,"maxLength":100,"pattern":"\\S"},
        "to":{"type":"array","minItems":2,"maxItems":2,"prefixItems":[{"type":"number","minimum":0,"maximum":720},{"$ref":"#/$defs/y"}],"items":false}
      }
    },
    "force": {
      "type":"object","additionalProperties":false,"required":["start","end","fx","at","label"],
      "properties":{
        "start":{"type":"number","minimum":0,"maximum":5000},"end":{"type":"number","minimum":0,"maximum":5000},
        "fx":{"type":"number","minimum":-0.1,"maximum":0.1},"fy":{"type":"number","minimum":-0.1,"maximum":0.1},
        "at":{"$ref":"#/$defs/point"},"label":{"type":"string","minLength":1,"maxLength":100,"pattern":"\\S"}
      }
    }
  }
}
