{
  "_comment": "PGS (PlayCraft Gameplay Schema) 参考骨架 - 泡泡射击消除玩法。Agent 以此为起点编写或 fork 玩法规则。",
  "version": "1.0",
  "gameplayType": "bubble_shooter",
  "entities": {
    "board": {
      "type": "grid",
      "layout": "rectangular",
      "cols": 9,
      "rows": 12,
      "description": "泡泡网格，奇数行与偶数行可以错位（六边形布局），board[row][col]，null 表示空格"
    },
    "bubble": {
      "type": "cell-item",
      "fields": {
        "color": "BubbleColor (0..colorCount-1)",
        "row": "number",
        "col": "number"
      },
      "description": "泡泡，有颜色，占据网格的一个格子"
    },
    "cannon": {
      "type": "shooter",
      "fields": {
        "currentColor": "BubbleColor (当前待发射的泡泡颜色)",
        "nextColor": "BubbleColor (下一颗泡泡颜色，供玩家预判)",
        "position": "{ x: number, y: number } (炮口位置，固定在底部中央)"
      },
      "description": "发射炮，持有当前和下一颗泡泡颜色"
    },
    "score": {
      "type": "counter",
      "initial": 0,
      "description": "当前得分"
    },
    "moves": {
      "type": "counter",
      "initial": 0,
      "description": "已发射次数，每发射 advanceRowAfterShots 次后推进一排"
    }
  },
  "rules": {
    "aim_and_shoot": {
      "description": "玩家指定瞄准角度，泡泡从炮口沿直线飞出，可在左右墙壁反弹",
      "trajectory": "angle-based ray with wall reflection",
      "wallBounce": true,
      "maxBounces": 3
    },
    "snap_to_grid": {
      "description": "飞行中的泡泡命中已有泡泡或顶部墙壁时，吸附到最近的空网格位置",
      "snapLogic": "find nearest empty cell adjacent to hit position"
    },
    "cluster_pop": {
      "description": "泡泡落定后，检测与落点颜色相同的连通区域（BFS）；若连通数量 >= minClusterSize，整簇消除",
      "minClusterSize": 3,
      "connectivity": "4-connected (or 6-connected for hex layout)",
      "scorePerBubble": 100
    },
    "orphan_drop": {
      "description": "消除后，从顶部行出发做 BFS；无法到达顶部的泡泡即为孤立泡泡，自动掉落",
      "trigger": "after_cluster_pop",
      "scorePerOrphan": 200,
      "dropAnimation": "fall-off-screen"
    },
    "advance_board": {
      "description": "每发射 advanceRowAfterShots 次后，顶部新增一排随机颜色泡泡，整体下移一行",
      "trigger": "shots_count % advanceRowAfterShots === 0",
      "newRowColors": "random from existing colors on board"
    }
  },
  "algorithm": {
    "description": "主循环：aim → shoot → snap → cluster_check → pop → orphan_drop → advance_check → win/lose",
    "steps": [
      "1. 玩家输入瞄准角度",
      "2. 计算弹道轨迹（含墙壁反弹），确定落点",
      "3. 泡泡吸附到最近空网格位置",
      "4. 更新发射次数（moves += 1）",
      "5. 以落点为起始，BFS 查找同色连通簇",
      "6. 若簇大小 >= minClusterSize → 整簇消除，计分",
      "7. 从顶部行 BFS，标记所有不可达泡泡为孤立泡泡",
      "8. 孤立泡泡掉落，计分",
      "9. 更新 cannon：currentColor = nextColor，随机生成新 nextColor",
      "10. 检查推进条件：moves % advanceRowAfterShots === 0 → 新增顶部一排",
      "11. 检查失败：最低泡泡行 >= cannon 所在行（泡泡触底）",
      "12. 检查胜利：棋盘所有泡泡已清空"
    ]
  },
  "winCondition": {
    "type": "board_cleared",
    "description": "棋盘上所有泡泡均已消除（board 全为 null）"
  },
  "loseCondition": {
    "type": "bubbles_reach_cannon_line",
    "description": "任意泡泡的 row >= loseRow（炮台所在行），游戏结束"
  },
  "testCases": [
    {
      "id": "tc_basic_3pop",
      "description": "基础三消：发射与已有同色泡泡相邻，凑成3个连通，整簇消除",
      "initialState": {
        "board": {
          "rows": [
            { "row": 0, "bubbles": [{ "col": 4, "color": 0 }, { "col": 5, "color": 0 }] }
          ]
        },
        "cannon": { "currentColor": 0, "nextColor": 1 },
        "score": 0,
        "moves": 0
      },
      "actions": [
        { "type": "shoot", "angle": 90, "_comment": "竖直向上，落在 row=0, col=4 附近" }
      ],
      "expectedState": {
        "poppedCount": 3,
        "scoreGained": 300,
        "boardEmpty": false,
        "gameOver": false
      }
    },
    {
      "id": "tc_wall_bounce_pop",
      "description": "墙壁反弹后命中泡泡并触发消除",
      "initialState": {
        "board": {
          "rows": [
            { "row": 0, "bubbles": [
              { "col": 8, "color": 2 },
              { "col": 7, "color": 2 }
            ]}
          ]
        },
        "cannon": { "currentColor": 2, "nextColor": 0 },
        "score": 0,
        "moves": 0
      },
      "actions": [
        { "type": "shoot", "angle": 45, "_comment": "斜向右上，反弹右壁后命中右侧同色泡泡" }
      ],
      "expectedState": {
        "poppedCount": { "min": 3 },
        "scoreGained": { "min": 300 },
        "gameOver": false
      }
    },
    {
      "id": "tc_orphan_drop",
      "description": "孤立掉落：消除支撑簇后，上方失去连接的泡泡自动掉落",
      "initialState": {
        "board": {
          "rows": [
            { "row": 0, "bubbles": [
              { "col": 4, "color": 0 },
              { "col": 5, "color": 0 }
            ]},
            { "row": 1, "bubbles": [
              { "col": 4, "color": 1 },
              { "col": 5, "color": 0 }
            ]},
            { "row": 2, "bubbles": [
              { "col": 4, "color": 0 }
            ]}
          ]
        },
        "cannon": { "currentColor": 0, "nextColor": 1 },
        "score": 0,
        "moves": 0
      },
      "actions": [
        { "type": "shoot", "angle": 90 }
      ],
      "expectedState": {
        "poppedCount": { "min": 3 },
        "orphanDropped": { "min": 1 },
        "scoreGained": { "min": 500 },
        "gameOver": false
      }
    },
    {
      "id": "tc_lose_condition",
      "description": "失败条件：泡泡推进后触碰炮台行",
      "initialState": {
        "board": {
          "rows": [
            { "row": 10, "bubbles": [{ "col": 0, "color": 3 }, { "col": 1, "color": 3 }, { "col": 2, "color": 3 }] },
            { "row": 11, "bubbles": [{ "col": 0, "color": 4 }] }
          ]
        },
        "cannon": { "currentColor": 5, "nextColor": 1 },
        "score": 0,
        "moves": 4,
        "loseRow": 12
      },
      "actions": [
        { "type": "shoot", "angle": 90 }
      ],
      "expectedState": {
        "gameOver": true,
        "win": false,
        "reason": "bubbles_reach_cannon_line"
      }
    },
    {
      "id": "tc_win_board_cleared",
      "description": "胜利条件：消除最后一组泡泡，棋盘清空",
      "initialState": {
        "board": {
          "rows": [
            { "row": 0, "bubbles": [{ "col": 4, "color": 1 }, { "col": 5, "color": 1 }] }
          ]
        },
        "cannon": { "currentColor": 1, "nextColor": 2 },
        "score": 0,
        "moves": 0
      },
      "actions": [
        { "type": "shoot", "angle": 90 }
      ],
      "expectedState": {
        "boardEmpty": true,
        "win": true,
        "gameOver": true,
        "poppedCount": 3
      }
    }
  ]
}
