---
name: pin_pull.aigameplay
description: 拔针解谜玩法封包 - 通过逐个拔出销钉引导液体流向目标容器、避开危险区域的 PGS 实现
triggers: 拔针,销钉,液体,流动,pin,pull,water,liquid,puzzle,引导,障碍,flow
---

# pin_pull.aigameplay

拔针解谜核心玩法封包。实现拔针引流规则的 PGS (PlayCraft Gameplay Schema, 玩法定义规范) JSON 和 reducer，玩家逐个拔出销钉开通流道，液体沿重力方向流动，目标是引导液体填满所有目标容器，同时避免触碰危险区域。

## Recipe

- **玩法类型**：pin_pull（拔针解谜）
- **参考文件**：
  - `ref/pgs-schema.json`：PGS (PlayCraft Gameplay Schema, 玩法定义规范) 规则骨架（entities、rules、algorithm、testCases）
  - `ref/reducer.template.ts`：reducer 接口约束模板（`init` / `dispatch` / `getState` + 分步函数）
- **核心规则**：
  - `liquidFlowEnabled`: true（液体沿已开放的通道流动）
  - `multiPinSequencing`: true（多销钉需要按顺序拔取）
  - `dangerZoneEnabled`: true（液体触碰危险区立即失败）
  - `gravitationalFlow`: true（液体从高到低自然流动）
- **PGS Agent 操作流程**：
  1. 读取 `ref/pgs-schema.json` 作为规则骨架起点
  2. 读取 `ref/reducer.template.ts` 了解接口约束
  3. 实现 `reducer.ts`，导出 `init(config)`、`dispatch(state, action)`、`getState(state)` 以及**全部分步函数**
  4. 运行 `ref/pgs-schema.json` 中的所有 `testCases`，全部通过后才算完成
  5. 更新 manifest 的 `generation.pgsVersion` 和 `result.outputFile`
- **可 fork 变体**：
  - 多液体类型：水/火/酸液，不同液体不能混合
  - 开关机关：销钉拔出后可重新插入
  - 时间限制：液体有限制时间内必须流到目标

## Result

- **产物文件**：`reducer.ts`
- **格式**：TypeScript，导出 `init(config)`、`dispatch(action)`、`getState()` + 分步函数接口
- **验证**：所有 testCases 必须通过

## 渲染集成（⚠️ 场景层必读）

### 设计原则：逻辑与动画分离

reducer 提供两套接口：

| 接口 | 适用场景 | 特点 |
|------|---------|------|
| `dispatch()` | 无头测试、AI 模拟、跳过动画 | 一步到位，只返回最终状态 |
| 分步函数 | **渲染层/GameScene** | 逐步执行，返回中间过程数据 |

### ⚠️ 禁止在渲染层使用 dispatch() 然后全量重建场景

正确做法是**分步驱动动画链**：

```ts
// ✅ 正确做法：逐步调用分步函数，每步之间插入动画

// Step 1: 检查销钉是否可拔
const canPull = canPullPin(state, pinId);
if (!canPull) { playLockedFeedback(); return; }

// Step 2: 拔出销钉，获取开放的通道信息
const { newState, openedChannels } = pullPin(state, pinId);
await playPinPullAnimation(pinId);  // 销钉拔出动画

// Step 3: 模拟液体流动，获取流动路径
const flowResult = simulateLiquidFlow(newState);
await playLiquidFlowAnimation(flowResult.paths);  // 液体流动动画（逐段播放）

// Step 4: 检查胜败条件
const result = checkWinLose(newState);
if (result.win) showWinPanel();
if (result.lose) showLosePanel('danger_zone_hit');
```

### 分步函数返回值与动画映射

| 分步函数 | 返回值 | 场景层动画 |
|---------|--------|-----------|
| `canPullPin()` | `boolean` | 不可拔时播放锁定/摇晃提示 |
| `pullPin()` | `{ newState, openedChannels }` | 销钉拔出 + 通道开放高亮 |
| `simulateLiquidFlow()` | `FlowResult` | 液体沿路径逐段填充动画 |
| `checkWinLose()` | `{ win, lose, reason }` | 目标容器满 = 胜利；触碰危险 = 失败 |

## Binding

- **Binding Role**：`gameplayRule`
- **挂载目标**：`game/gameplay/pin_pull/reducer.ts`
- **引用类型**：`gameplay-module-reference`
- **接口契约**：`init(config)` / `dispatch(action)` / `getState()` / `canPullPin()` / `pullPin()` / `simulateLiquidFlow()` / `checkWinLose()`

## Skill Definition

tools:
  - bash
  - write
  - read
prompt_extension: |
  You are implementing a pin pull liquid flow puzzle gameplay module following the PGS specification.
  Write the PGS JSON with entities (pins, liquid_sources, containers, channels, obstacles), rules, algorithm, and testCases.
  Implement the reducer.ts with:
    - init/dispatch/getState (一体化接口，用于测试)
    - canPullPin/pullPin/simulateLiquidFlow/checkWinLose (分步函数，用于渲染层动画驱动)
  Key constraints:
    - Liquid flows from source through open channels (pins removed = channel open)
    - Win condition: all target containers are filled
    - Lose condition: liquid reaches any danger zone
    - Liquid flow is deterministic given the current channel graph
  The step-by-step functions are CRITICAL — the rendering layer (GameScene) must use them to drive animations.
  Run the testCases to verify the logic before finalizing.
  Apply the Binding to wire the reducer into the game's gameplay module path.
