{
  "_comment": "PGS (PlayCraft Gameplay Schema) 参考骨架 - 篮球投篮玩法。Agent 以此为起点编写或 fork 玩法规则。",
  "version": "1.0",
  "gameplayType": "basketball_shot",
  "entities": {
    "hoop": {
      "type": "target",
      "description": "篮筐，固定位置",
      "fields": {
        "position": { "type": "vector2", "description": "篮筐中心坐标 {x, y}" },
        "radius":   { "type": "number",  "value": 45, "description": "篮筐内径半径（像素）" },
        "rimY":     { "type": "number",  "description": "篮筐顶部 y 坐标，球必须从上方穿过此线" }
      }
    },
    "ball": {
      "type": "projectile",
      "description": "篮球，抛物线运动",
      "fields": {
        "position": { "type": "vector2", "description": "当前位置 {x, y}" },
        "velocity": { "type": "vector2", "description": "当前速度 {vx, vy}" },
        "inFlight": { "type": "boolean", "default": false }
      }
    },
    "player_position": {
      "type": "spawn-point",
      "description": "玩家投篮位置（不同关卡有不同距离/角度）",
      "fields": {
        "position": { "type": "vector2" },
        "distance":  { "type": "number", "description": "距篮筐距离（米），影响难度" }
      }
    },
    "score": {
      "type": "counter",
      "initial": 0,
      "description": "当前得分"
    },
    "shots_remaining": {
      "type": "counter",
      "description": "剩余投篮次数",
      "initial": 10
    },
    "combo_count": {
      "type": "counter",
      "initial": 0,
      "description": "当前连续命中次数"
    }
  },
  "rules": {
    "aim": {
      "description": "玩家设置投篮角度和力度",
      "fields": {
        "angle": { "type": "number", "range": [10, 85], "description": "投篮仰角（度），相对于水平方向" },
        "power": { "type": "number", "range": [0, 100], "description": "力度百分比，决定初速度大小" }
      }
    },
    "shoot": {
      "description": "将力度和角度转换为初始速度向量，球开始飞行",
      "formula": {
        "vx": "cos(angle) × power × maxSpeed",
        "vy": "-sin(angle) × power × maxSpeed"
      },
      "effect": "ball.inFlight = true; shots_remaining--"
    },
    "trajectory": {
      "description": "抛物线物理模拟",
      "formula": {
        "x(t)": "x0 + vx × t",
        "y(t)": "y0 + vy × t + 0.5 × gravity × t²",
        "vy(t)": "vy0 + gravity × t"
      },
      "gravity": 980,
      "description2": "重力单位 px/s²，每帧 dt 秒更新位置"
    },
    "hoop_check": {
      "description": "入筐检测：球从 rimY 上方穿越到下方，且球心 x 在筐内",
      "conditions": [
        "ball.prev_y < hoop.rimY AND ball.y >= hoop.rimY",
        "abs(ball.x - hoop.position.x) <= hoop.radius × (1 + rimTolerance)"
      ],
      "rimTolerance": 0.15,
      "scoreOnHit": "baseScore × comboMultiplier",
      "baseScore": 100
    },
    "rim_miss": {
      "description": "球碰到篮筐边缘但未入筐（可选：添加弹跳效果）",
      "trigger": "ball within rim band but fails hoop_check",
      "effect": "ball bounces off rim (optional visual)"
    },
    "combo": {
      "description": "连续命中加成：每次命中 combo_count++，倍率 = 1 + combo_count × 0.5",
      "comboMultiplierStep": 0.5,
      "maxMultiplier": 5.0,
      "resetOnMiss": true
    },
    "miss": {
      "description": "球落地或飞出边界且未入筐",
      "effect": "combo_count = 0"
    }
  },
  "algorithm": {
    "description": "主循环：aim → shoot → simulate_trajectory → hoop_check → score → repeat",
    "steps": [
      "1. 玩家设置投篮角度和力度",
      "2. 确认投篮：shots_remaining--，计算初始速度向量",
      "3. 每帧更新球的位置（抛物线物理：位移 = 速度×dt，vy += gravity×dt）",
      "4. 每帧检测入筐：球穿过 rimY 且 x 在筐内",
      "5. 入筐：score += baseScore × comboMultiplier；combo_count++；ball 重置",
      "6. 落地/出界：combo_count = 0；ball 重置",
      "7. 检测胜负：shots_remaining==0 时检查 score >= goalScore"
    ]
  },
  "winCondition": {
    "type": "score_goal",
    "description": "在 shots_remaining 耗尽前达到 goalScore（由 config 中定义）"
  },
  "loseCondition": {
    "type": "shots_exhausted",
    "description": "shots_remaining == 0 且 score < goalScore"
  },
  "testCases": [
    {
      "id": "tc_perfect_shot",
      "description": "完美入筐：正确角度和力度，球穿过篮筐中心",
      "initialState": {
        "ball": { "position": { "x": 200, "y": 600 }, "inFlight": false },
        "hoop": { "position": { "x": 600, "y": 300 }, "radius": 45, "rimY": 300 },
        "score": 0,
        "shots_remaining": 5,
        "combo_count": 0
      },
      "actions": [
        { "type": "aim",   "angle": 45, "power": 65 },
        { "type": "shoot" },
        { "type": "simulate_until_land" }
      ],
      "expectedState": {
        "scored": true,
        "score": 100,
        "combo_count": 1,
        "shots_remaining": 4
      }
    },
    {
      "id": "tc_rim_miss",
      "description": "打铁：球碰到篮筐边缘但未入筐",
      "initialState": {
        "ball": { "position": { "x": 200, "y": 600 }, "inFlight": false },
        "hoop": { "position": { "x": 600, "y": 300 }, "radius": 45, "rimY": 300 },
        "score": 0,
        "shots_remaining": 5,
        "combo_count": 2
      },
      "actions": [
        { "type": "aim",   "angle": 45, "power": 50 },
        { "type": "shoot" },
        { "type": "simulate_until_land" }
      ],
      "expectedState": {
        "scored": false,
        "combo_count": 0,
        "shots_remaining": 4
      }
    },
    {
      "id": "tc_combo_multiplier",
      "description": "连击加成：连续命中3次，第3次得分翻倍",
      "initialState": {
        "ball": { "position": { "x": 200, "y": 600 }, "inFlight": false },
        "hoop": { "position": { "x": 600, "y": 300 }, "radius": 45, "rimY": 300 },
        "score": 200,
        "shots_remaining": 5,
        "combo_count": 2
      },
      "actions": [
        { "type": "aim",   "angle": 45, "power": 65 },
        { "type": "shoot" },
        { "type": "simulate_until_land" }
      ],
      "expectedState": {
        "scored": true,
        "combo_count": 3,
        "score": 400,
        "score_gained": 200
      }
    },
    {
      "id": "tc_win",
      "description": "胜利：最后一投命中，总分达到目标",
      "initialState": {
        "score": 900,
        "goalScore": 1000,
        "shots_remaining": 1,
        "combo_count": 0
      },
      "actions": [
        { "type": "aim",   "angle": 45, "power": 65 },
        { "type": "shoot" },
        { "type": "simulate_until_land" }
      ],
      "expectedState": {
        "scored": true,
        "gameOver": true,
        "win": true,
        "score": { "min": 1000 }
      }
    },
    {
      "id": "tc_lose",
      "description": "失败：最后一投未中，分数不达标",
      "initialState": {
        "score": 500,
        "goalScore": 1000,
        "shots_remaining": 1,
        "combo_count": 0
      },
      "actions": [
        { "type": "aim",   "angle": 20, "power": 30 },
        { "type": "shoot" },
        { "type": "simulate_until_land" }
      ],
      "expectedState": {
        "scored": false,
        "shots_remaining": 0,
        "gameOver": true,
        "win": false
      }
    }
  ]
}
