{
  "_comment": "PGS (PlayCraft Gameplay Schema) 参考骨架 - 8球台球简化版。Agent 以此为起点编写或 fork 玩法规则。",
  "version": "1.0",
  "gameplayType": "eight_ball_pool",
  "entities": {
    "table": {
      "type": "play-field",
      "width": 1000,
      "height": 500,
      "description": "台球桌，包含6个球袋",
      "pockets": [
        { "id": "top_left",     "x": 0,    "y": 0   },
        { "id": "top_mid",      "x": 500,  "y": 0   },
        { "id": "top_right",    "x": 1000, "y": 0   },
        { "id": "bot_left",     "x": 0,    "y": 500 },
        { "id": "bot_mid",      "x": 500,  "y": 500 },
        { "id": "bot_right",    "x": 1000, "y": 500 }
      ],
      "pocketRadius": 28
    },
    "ball": {
      "type": "physics-object",
      "radius": 14,
      "description": "台球实体，包含白球(cue)、实心球(solid,1-7)、条纹球(stripe,9-15)、黑8(eight)",
      "fields": {
        "id":       { "type": "integer",  "range": [0, 15], "description": "0=白球，1-7=实心，8=黑球，9-15=条纹" },
        "type":     { "type": "enum",     "values": ["cue", "solid", "stripe", "eight"] },
        "position": { "type": "vector2",  "description": "球心坐标 {x, y}" },
        "velocity": { "type": "vector2",  "description": "当前速度 {vx, vy}，单位 px/s" },
        "state":    { "type": "enum",     "values": ["on_table", "pocketed"], "default": "on_table" }
      }
    },
    "aim": {
      "type": "input-state",
      "description": "当前瞄准状态",
      "fields": {
        "angle": { "type": "number",  "range": [0, 360], "description": "击球方向角度（度），0=向右" },
        "power": { "type": "number",  "range": [0, 100], "description": "力度百分比" }
      }
    },
    "turn_state": {
      "type": "game-state",
      "description": "回合状态",
      "fields": {
        "playerGroup": { "type": "enum",    "values": ["solid", "stripe", "unassigned"], "default": "unassigned" },
        "pocketedOwn": { "type": "integer", "default": 0, "description": "已入袋的己方球数量" },
        "phase":       { "type": "enum",    "values": ["assign", "clear_own", "sink_eight"], "default": "assign" },
        "ballsMoving": { "type": "boolean", "default": false },
        "shotInProgress": { "type": "boolean", "default": false }
      }
    },
    "score": {
      "type": "counter",
      "initial": 0,
      "description": "当前得分（每入袋己方球+100，消灭对方球-50，仅用于展示）"
    }
  },
  "rules": {
    "aim_cue_ball": {
      "description": "玩家设置白球击打角度和力度，不消耗回合",
      "trigger": "player_input",
      "effect": "update aim.angle and aim.power"
    },
    "shoot": {
      "description": "玩家确认击打：给白球施加速度 velocity = power × maxSpeed × direction(angle)",
      "trigger": "player_confirm",
      "effect": "cue_ball.velocity = powerToVelocity(aim.power, aim.angle); turn_state.shotInProgress = true"
    },
    "physics_step": {
      "description": "每帧更新：所有球按速度移动，速度每帧乘以 frictionFactor（约0.985），低于阈值停止",
      "frictionFactor": 0.985,
      "stopThreshold": 1,
      "wallBounce": true,
      "ballCollision": "elastic"
    },
    "pocket_check": {
      "description": "球心进入任意球袋半径内则进袋；白球进袋=犯规（scratch）",
      "pocketRadius": 28,
      "effects": {
        "own_group_ball":  "ball.state=pocketed, pocketedOwn++, score+100",
        "other_group_ball":"ball.state=pocketed（对方获利）",
        "eight_ball_early":"game_over(lose) unless all own balls pocketed",
        "eight_ball_win":  "game_over(win) if all own balls pocketed AND phase==sink_eight",
        "cue_ball":        "scratch: cue ball respawn at kitchen area"
      }
    },
    "turn_switch": {
      "description": "所有球停止后检查回合结果：若入袋>=1个己方球则继续，否则结束本次回合",
      "trigger": "all_balls_stopped"
    },
    "group_assign": {
      "description": "首次入袋时根据球类型分配玩家球组",
      "trigger": "first_pocket_event",
      "effect": "playerGroup = ball.type (solid or stripe)"
    }
  },
  "algorithm": {
    "description": "主循环：aim → shoot → physics → pocket_check → turn_switch",
    "steps": [
      "1. 玩家调整瞄准参数（angle, power），可多次设置",
      "2. 玩家确认击打，给白球赋予初始速度",
      "3. 物理帧循环：移动所有球、处理碰撞、处理边界反弹",
      "4. 每帧检测所有球是否进入球袋",
      "5. 所有球停止后进行入袋判定：己方球入袋加分，犯规处理",
      "6. 若 phase==assign 且首次有球入袋，分配玩家球组",
      "7. 检测胜利（phase==sink_eight 且8号球入袋）或失败（8号球提前入袋/scratch on 8ball shot）",
      "8. 回合切换，等待下一次击球"
    ]
  },
  "winCondition": {
    "type": "pocket_eight_ball",
    "description": "先将己方7个球（实心或条纹）全部打入球袋，再将8号黑球打入球袋"
  },
  "loseCondition": {
    "type": "foul_on_eight",
    "description": "在己方球未全部入袋时打入8号球，或在击打8号球时白球入袋（scratch）"
  },
  "testCases": [
    {
      "id": "tc_pocket_own_ball",
      "description": "成功入袋己方球（实心球）：白球击中1号实心球，1号球进袋",
      "initialState": {
        "balls": [
          { "id": 0, "type": "cue",   "position": { "x": 300, "y": 250 }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" },
          { "id": 1, "type": "solid", "position": { "x": 30,  "y": 30  }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" }
        ],
        "turn_state": { "playerGroup": "solid", "pocketedOwn": 0, "phase": "clear_own" },
        "aim": { "angle": 315, "power": 80 }
      },
      "actions": [
        { "type": "shoot" },
        { "type": "simulate_until_stop" }
      ],
      "expectedState": {
        "ball_1_state": "pocketed",
        "turn_state.pocketedOwn": 1,
        "score_gained": 100
      }
    },
    {
      "id": "tc_win_sink_eight",
      "description": "胜利条件：己方7球已全入袋，成功打入8号球",
      "initialState": {
        "balls": [
          { "id": 0, "type": "cue",   "position": { "x": 500, "y": 250 }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" },
          { "id": 8, "type": "eight", "position": { "x": 480, "y": 25  }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" }
        ],
        "turn_state": { "playerGroup": "solid", "pocketedOwn": 7, "phase": "sink_eight" },
        "aim": { "angle": 90, "power": 70 }
      },
      "actions": [
        { "type": "shoot" },
        { "type": "simulate_until_stop" }
      ],
      "expectedState": {
        "gameOver": true,
        "win": true,
        "ball_8_state": "pocketed"
      }
    },
    {
      "id": "tc_scratch_lose",
      "description": "失败：击打8号球时白球入袋（scratch on 8ball shot）",
      "initialState": {
        "balls": [
          { "id": 0, "type": "cue",   "position": { "x": 500, "y": 470 }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" },
          { "id": 8, "type": "eight", "position": { "x": 500, "y": 400 }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" }
        ],
        "turn_state": { "playerGroup": "solid", "pocketedOwn": 7, "phase": "sink_eight" },
        "aim": { "angle": 90, "power": 100 }
      },
      "actions": [
        { "type": "shoot" },
        { "type": "simulate_until_stop" }
      ],
      "expectedState": {
        "gameOver": true,
        "win": false,
        "loseReason": "scratch_on_eight_ball"
      }
    },
    {
      "id": "tc_early_eight_lose",
      "description": "失败：己方球未清空时打入8号球",
      "initialState": {
        "balls": [
          { "id": 0, "type": "cue",   "position": { "x": 500, "y": 250 }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" },
          { "id": 8, "type": "eight", "position": { "x": 480, "y": 25  }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" },
          { "id": 1, "type": "solid", "position": { "x": 600, "y": 250 }, "velocity": { "vx": 0, "vy": 0 }, "state": "on_table" }
        ],
        "turn_state": { "playerGroup": "solid", "pocketedOwn": 6, "phase": "clear_own" },
        "aim": { "angle": 90, "power": 70 }
      },
      "actions": [
        { "type": "shoot" },
        { "type": "simulate_until_stop" }
      ],
      "expectedState": {
        "gameOver": true,
        "win": false,
        "loseReason": "eight_ball_pocketed_early"
      }
    }
  ]
}
