{
  "_comment": "PGS (PlayCraft Gameplay Schema) 参考骨架 - 螺丝拆解玩法。Agent 以此为起点编写或 fork 玩法规则。",
  "version": "1.0",
  "gameplayType": "screw_puzzle",
  "entities": {
    "board": {
      "type": "layered-scene",
      "layers": 3,
      "description": "多层场景，每层有若干固定位置的零件槽，layer 0 为最底层"
    },
    "screw": {
      "type": "interactive-item",
      "fields": {
        "id": "string",
        "color": "ScrewColor (red|blue|green|yellow|purple|orange)",
        "itemId": "string (所属零件)",
        "holeId": "string | null (当前插入的螺孔，null 表示在缓冲区)"
      },
      "description": "螺丝，有颜色标记，固定零件到对应螺孔；只能被移入同色螺孔"
    },
    "item": {
      "type": "multi-cell-object",
      "fields": {
        "id": "string",
        "layer": "number (所在层级，越小越底层)",
        "screwIds": "string[] (固定此零件的螺丝列表，全部移除后零件释放)",
        "state": "ItemState (fixed|releasing|released)"
      },
      "description": "零件/木板，占据一个或多个格子，被若干螺丝固定，所有螺丝拆除后因重力释放"
    },
    "screw_hole": {
      "type": "slot",
      "fields": {
        "id": "string",
        "color": "ScrewColor",
        "itemId": "string (所属零件)",
        "screwId": "string | null (当前插入的螺丝)"
      },
      "description": "螺孔（接收槽），有颜色标记，只接受同色螺丝"
    },
    "buffer": {
      "type": "queue",
      "capacity": 3,
      "description": "临时缓冲区，存放已拆但尚未插入螺孔的螺丝；超出容量且无法匹配时触发失败"
    }
  },
  "rules": {
    "remove_screw": {
      "description": "玩家点击螺丝 → 螺丝从当前螺孔拔出，进入缓冲区或直接移入同色空螺孔",
      "trigger": "player_tap",
      "steps": [
        "1. 从螺孔拔出螺丝（screwHole.screwId = null）",
        "2. 查找任意一个同色且为空的螺孔（非当前零件上的）",
        "3. 若找到 → 螺丝直接进入该螺孔（screwHole.screwId = screwId）",
        "4. 若未找到 → 螺丝进入 buffer",
        "5. buffer 中的螺丝在每次操作后尝试自动匹配空螺孔（先入先出）"
      ]
    },
    "gravity_release": {
      "description": "零件上所有螺丝均被移除 → 零件释放（下落/退出场景）",
      "trigger": "item.screwIds.every(id => screwNotInHole)",
      "effect": "item.state = 'releasing' → 播放下落动画 → item.state = 'released' → 从场景移除"
    },
    "buffer_overflow": {
      "description": "缓冲区满（3 个螺丝）且当前操作的螺丝无同色空螺孔时，触发失败",
      "trigger": "buffer.length >= buffer.capacity AND no_matching_hole",
      "effect": "gameOver = true, win = false"
    },
    "cascade_match": {
      "description": "每次操作结束后，缓冲区内的螺丝自动尝试匹配空螺孔并插入（先入先出）",
      "trigger": "after_any_action",
      "effect": "auto-fill matching screw holes from buffer"
    }
  },
  "algorithm": {
    "description": "主循环：tap_screw → remove → match_hole / buffer → release_item → gravity → win/lose 检测",
    "steps": [
      "1. 玩家点击目标螺丝",
      "2. 检查螺丝是否可拆（当前状态合法）",
      "3. 将螺丝从螺孔拔出",
      "4. 查找同色空螺孔（优先非当前零件），有则直接插入，无则进入 buffer",
      "5. 检查 buffer 是否已满且无法匹配 → 失败",
      "6. 检查 buffer 中所有螺丝，自动匹配并插入空螺孔",
      "7. 检查所有零件：若某零件的全部螺丝都已被移除（不在任何螺孔），则触发释放",
      "8. 对已释放零件执行重力模拟（上层零件下移）",
      "9. 检测胜利条件：所有零件 state = 'released'"
    ]
  },
  "winCondition": {
    "type": "all_items_released",
    "description": "所有零件 state = 'released'（已下落退出场景）"
  },
  "loseCondition": {
    "type": "buffer_overflow",
    "description": "缓冲区已满（3 个螺丝）且当前移除的螺丝无法匹配任何空螺孔"
  },
  "testCases": [
    {
      "id": "tc_basic_remove",
      "description": "基础移除：点击螺丝，直接飞入同色空螺孔，缓冲区不变",
      "initialState": {
        "screws": [
          { "id": "s1", "color": "red", "itemId": "item1", "holeId": "h1" }
        ],
        "screwHoles": [
          { "id": "h1", "color": "red", "itemId": "item1", "screwId": "s1" },
          { "id": "h2", "color": "red", "itemId": "item2", "screwId": null }
        ],
        "items": [
          { "id": "item1", "layer": 1, "screwIds": ["s1"], "state": "fixed" },
          { "id": "item2", "layer": 0, "screwIds": [], "state": "fixed" }
        ],
        "buffer": []
      },
      "actions": [
        { "type": "remove_screw", "screwId": "s1" }
      ],
      "expectedState": {
        "screws": [
          { "id": "s1", "holeId": "h2" }
        ],
        "screwHoles": [
          { "id": "h1", "screwId": null },
          { "id": "h2", "screwId": "s1" }
        ],
        "buffer": [],
        "items": [
          { "id": "item1", "state": "released" }
        ],
        "gameOver": false,
        "win": false
      }
    },
    {
      "id": "tc_gravity_release",
      "description": "重力释放：移除零件所有螺丝后，零件状态变为 releasing/released",
      "initialState": {
        "screws": [
          { "id": "s1", "color": "blue", "itemId": "item1", "holeId": "h1" },
          { "id": "s2", "color": "blue", "itemId": "item1", "holeId": "h2" }
        ],
        "screwHoles": [
          { "id": "h1", "color": "blue", "itemId": "item1", "screwId": "s1" },
          { "id": "h2", "color": "blue", "itemId": "item1", "screwId": "s2" },
          { "id": "h3", "color": "blue", "itemId": "item2", "screwId": null },
          { "id": "h4", "color": "blue", "itemId": "item2", "screwId": null }
        ],
        "items": [
          { "id": "item1", "layer": 1, "screwIds": ["s1", "s2"], "state": "fixed" },
          { "id": "item2", "layer": 0, "screwIds": [], "state": "released" }
        ],
        "buffer": []
      },
      "actions": [
        { "type": "remove_screw", "screwId": "s1" },
        { "type": "remove_screw", "screwId": "s2" }
      ],
      "expectedState": {
        "items": [
          { "id": "item1", "state": "released" }
        ],
        "buffer": [],
        "win": true,
        "gameOver": true
      }
    },
    {
      "id": "tc_color_mismatch_buffer",
      "description": "颜色不匹配：螺丝无法插入异色螺孔，进入缓冲区",
      "initialState": {
        "screws": [
          { "id": "s1", "color": "red", "itemId": "item1", "holeId": "h1" }
        ],
        "screwHoles": [
          { "id": "h1", "color": "red", "itemId": "item1", "screwId": "s1" },
          { "id": "h2", "color": "blue", "itemId": "item2", "screwId": null }
        ],
        "items": [
          { "id": "item1", "layer": 1, "screwIds": ["s1"], "state": "fixed" },
          { "id": "item2", "layer": 0, "screwIds": ["s2"], "state": "fixed" }
        ],
        "buffer": []
      },
      "actions": [
        { "type": "remove_screw", "screwId": "s1" }
      ],
      "expectedState": {
        "buffer": [{ "screwId": "s1", "color": "red" }],
        "screwHoles": [
          { "id": "h1", "screwId": null },
          { "id": "h2", "screwId": null }
        ],
        "gameOver": false,
        "win": false
      }
    },
    {
      "id": "tc_buffer_overflow_lose",
      "description": "缓冲区溢出失败：缓冲区已满3个，再拆一颗无同色空孔的螺丝 → 失败",
      "initialState": {
        "screws": [
          { "id": "s1", "color": "red", "itemId": "item1", "holeId": "h1" },
          { "id": "s2", "color": "green", "itemId": "item2", "holeId": "h2" },
          { "id": "s3", "color": "yellow", "itemId": "item3", "holeId": "h3" },
          { "id": "s4", "color": "purple", "itemId": "item4", "holeId": "h4" }
        ],
        "screwHoles": [
          { "id": "h1", "color": "red", "itemId": "item1", "screwId": "s1" },
          { "id": "h2", "color": "green", "itemId": "item2", "screwId": "s2" },
          { "id": "h3", "color": "yellow", "itemId": "item3", "screwId": "s3" },
          { "id": "h4", "color": "purple", "itemId": "item4", "screwId": "s4" }
        ],
        "items": [
          { "id": "item1", "layer": 2, "screwIds": ["s1"], "state": "fixed" },
          { "id": "item2", "layer": 2, "screwIds": ["s2"], "state": "fixed" },
          { "id": "item3", "layer": 1, "screwIds": ["s3"], "state": "fixed" },
          { "id": "item4", "layer": 1, "screwIds": ["s4"], "state": "fixed" }
        ],
        "buffer": [
          { "screwId": "sx1", "color": "orange" },
          { "screwId": "sx2", "color": "orange" },
          { "screwId": "sx3", "color": "orange" }
        ]
      },
      "actions": [
        { "type": "remove_screw", "screwId": "s1" }
      ],
      "expectedState": {
        "gameOver": true,
        "win": false,
        "reason": "buffer_overflow"
      }
    }
  ]
}
