{
  "_comment": "PGS (PlayCraft Gameplay Schema) 参考骨架 - 拔针解谜玩法。Agent 以此为起点编写或 fork 玩法规则。",
  "version": "1.0",
  "gameplayType": "pin_pull",
  "entities": {
    "pin": {
      "type": "interactive-item",
      "fields": {
        "id": "string",
        "position": "{ x: number, y: number }",
        "state": "PinState (inserted|removed)",
        "blockedChannelIds": "string[] (此销钉封堵的通道列表)"
      },
      "description": "销钉，插入状态时封堵关联通道；拔出后通道开放，液体可流过"
    },
    "liquid_source": {
      "type": "source-node",
      "fields": {
        "id": "string",
        "type": "LiquidType (water|blood|acid|lava)",
        "position": "{ x: number, y: number }",
        "capacity": "number (总量，-1 = 无限)",
        "remaining": "number"
      },
      "description": "液体来源节点，液体从此节点沿开放通道流动"
    },
    "container": {
      "type": "target-node",
      "fields": {
        "id": "string",
        "role": "ContainerRole (target|danger|neutral)",
        "acceptedLiquid": "LiquidType | 'any'",
        "capacity": "number",
        "filled": "number",
        "position": "{ x: number, y: number }"
      },
      "description": "容器节点：target = 需要被填满（胜利条件）；danger = 触碰则失败；neutral = 中性溢出"
    },
    "channel": {
      "type": "edge",
      "fields": {
        "id": "string",
        "from": "string (node id)",
        "to": "string (node id)",
        "state": "ChannelState (open|blocked)",
        "blockingPinIds": "string[] (封堵此通道的销钉，为空时通道开放)"
      },
      "description": "通道（有向边），连接节点；所有封堵销钉被移除后状态变为 open"
    },
    "obstacle": {
      "type": "static-blocker",
      "fields": {
        "id": "string",
        "blockedChannelIds": "string[]"
      },
      "description": "静态障碍，永久封堵通道（不可移除）"
    }
  },
  "rules": {
    "pull_pin": {
      "description": "玩家点击销钉 → 销钉状态变为 removed → 关联通道的 blockingPinIds 移除此销钉 → 若 blockingPinIds 为空则通道开放",
      "trigger": "player_tap",
      "effect": "pin.state = 'removed'; channel.state = 'open' if channel.blockingPinIds.length === 0"
    },
    "liquid_flow": {
      "description": "液体从 source 沿所有 open 通道，按重力方向（BFS/DFS）流向可达节点，填充 container",
      "trigger": "after_pull_pin",
      "algorithm": "graph BFS from all active sources through open channels",
      "effect": "container.filled += flowAmount; liquid_source.remaining -= flowAmount"
    },
    "win_check": {
      "description": "所有 target 容器 filled >= capacity",
      "trigger": "after_liquid_flow",
      "effect": "gameOver = true, win = true"
    },
    "lose_check": {
      "description": "液体流入任意 danger 容器（filled > 0）",
      "trigger": "after_liquid_flow",
      "effect": "gameOver = true, win = false"
    }
  },
  "algorithm": {
    "description": "主循环：tap_pin → open_channel → flow_liquid → win/lose 检测",
    "steps": [
      "1. 玩家点击目标销钉",
      "2. 检查销钉是否处于 inserted 状态（未被拔出）",
      "3. 将销钉状态改为 removed",
      "4. 更新关联通道的 blockingPinIds，若清空则通道变为 open",
      "5. 执行液体流动模拟（BFS 从所有 source 出发，经 open 通道到达节点）",
      "6. 更新所有容器的 filled 值",
      "7. 检查 lose 条件：any danger container filled > 0",
      "8. 检查 win 条件：all target containers filled >= capacity",
      "9. 若 win/lose 均未触发，等待下一次操作"
    ]
  },
  "winCondition": {
    "type": "all_targets_filled",
    "description": "所有 role='target' 的容器 filled >= capacity"
  },
  "loseCondition": {
    "type": "danger_zone_reached",
    "description": "任意 role='danger' 容器的 filled > 0"
  },
  "testCases": [
    {
      "id": "tc_single_pin_win",
      "description": "单销钉路径：拔出唯一销钉，液体直达目标容器，立即胜利",
      "initialState": {
        "pins": [
          { "id": "p1", "state": "inserted", "blockedChannelIds": ["c1"] }
        ],
        "liquid_sources": [
          { "id": "src1", "type": "water", "capacity": -1, "remaining": -1 }
        ],
        "containers": [
          { "id": "cup1", "role": "target", "capacity": 100, "filled": 0 }
        ],
        "channels": [
          { "id": "c1", "from": "src1", "to": "cup1", "state": "blocked", "blockingPinIds": ["p1"] }
        ]
      },
      "actions": [
        { "type": "pull_pin", "pinId": "p1" }
      ],
      "expectedState": {
        "pins": [{ "id": "p1", "state": "removed" }],
        "channels": [{ "id": "c1", "state": "open" }],
        "containers": [{ "id": "cup1", "filled": 100 }],
        "win": true,
        "gameOver": true
      }
    },
    {
      "id": "tc_wrong_order_lose",
      "description": "错误顺序：先拔下方销钉，液体流向危险区，立即失败",
      "initialState": {
        "pins": [
          { "id": "p_top", "state": "inserted", "blockedChannelIds": ["c_safe"] },
          { "id": "p_bottom", "state": "inserted", "blockedChannelIds": ["c_danger"] }
        ],
        "liquid_sources": [
          { "id": "src1", "type": "water", "capacity": -1, "remaining": -1 }
        ],
        "containers": [
          { "id": "target1", "role": "target", "capacity": 100, "filled": 0 },
          { "id": "danger1", "role": "danger", "capacity": 999, "filled": 0 }
        ],
        "channels": [
          { "id": "c_safe", "from": "src1", "to": "target1", "state": "blocked", "blockingPinIds": ["p_top"] },
          { "id": "c_danger", "from": "src1", "to": "danger1", "state": "blocked", "blockingPinIds": ["p_bottom"] }
        ]
      },
      "actions": [
        { "type": "pull_pin", "pinId": "p_bottom" }
      ],
      "expectedState": {
        "containers": [
          { "id": "target1", "filled": 0 },
          { "id": "danger1", "filled": { "min": 1 } }
        ],
        "win": false,
        "gameOver": true,
        "reason": "danger_zone_reached"
      }
    },
    {
      "id": "tc_multi_step_sequence",
      "description": "多步序列：需按顺序拔出 p1、p2，液体绕过危险区到达目标",
      "initialState": {
        "pins": [
          { "id": "p1", "state": "inserted", "blockedChannelIds": ["c1"] },
          { "id": "p2", "state": "inserted", "blockedChannelIds": ["c2"] }
        ],
        "liquid_sources": [
          { "id": "src1", "type": "water", "capacity": -1, "remaining": -1 }
        ],
        "containers": [
          { "id": "target1", "role": "target", "capacity": 100, "filled": 0 },
          { "id": "danger1", "role": "danger", "capacity": 999, "filled": 0 }
        ],
        "channels": [
          { "id": "c1", "from": "src1", "to": "node_mid", "state": "blocked", "blockingPinIds": ["p1"] },
          { "id": "c2", "from": "node_mid", "to": "target1", "state": "blocked", "blockingPinIds": ["p2"] },
          { "id": "c3", "from": "node_mid", "to": "danger1", "state": "open", "blockingPinIds": [] }
        ]
      },
      "actions": [
        { "type": "pull_pin", "pinId": "p2" },
        { "type": "pull_pin", "pinId": "p1" }
      ],
      "expectedState": {
        "containers": [
          { "id": "target1", "filled": 100 },
          { "id": "danger1", "filled": 0 }
        ],
        "win": true,
        "gameOver": true
      }
    }
  ]
}
