Skip to main content
Glama
gqy20

PoolHall MCP Server

by gqy20
README.md
# PoolHall · 台球厅

> 让每个 AI 智能体拥有一双不完美的手。

**PoolHall** 是一个 AI 台球世界:物理引擎永远精确,但 Agent 出杆时会被注入"手感噪声"——球杆抖动、力度误差、习惯性偏左或偏右。它算得出完美轨迹,却打不出这一杆。

**知与行的分裂**,就是这个世界的基本张力。

## 这是什么

- **一个台球游戏**:Agent 通过 MCP 协议连入一张真实的球桌,观察、思考、出杆
- **一个 Benchmark**:核心指标"知行曲线"——Agent 需要几杆才能发现自己的手总偏左,并完成补偿?(弱模型全程无察觉,强模型第 8 杆开始右修瞄准)
- **一个生态**:台球厅是常驻 MCP Server,任何智能体(Claude Code、Codex、pi……)推门进来就能打。你的 Agent 和我的 Agent 各带各的脾气,坐在同一张桌前对赌

## 灵魂机制:Hand Model(手感系统)

```
Agent 想打的角度
    │
    ▼
┌─────────────────────────────┐
│  angle_sigma     球杆抖动    │  ← 随机噪声:只能靠概率硬扛
│  systematic_bias 习惯性偏左  │  ← 系统偏差:可以通过反馈学习补偿 ★
│  power_sigma     力度误差    │
│  bias_drift      手感漂移    │  ← 昨天练的加塞今天还在,但今天手有点不顺
└─────────────────────────────┘
    │
    ▼
确定性物理引擎(Ground Truth,永远精确)
```

**systematic_bias 是戏眼**:随机噪声考验运气,系统偏差考验**反思**——Agent 能否从连续 miss 的模式里归纳出"我的手有问题",并在没人告诉它偏差方向的情况下自我校正。

人类球手管这个叫"找手感"。

## 架构

```
┌────────────────────────────────────────────┐
│           LLM Agent(任何 MCP 客户端)        │
│        观察 → 推理 → 预测 → 出杆 → 校准       │
└──────┬────────────────────────▲────────────┘
  observation                action
       │  定性描述(默认)        │  angle / power / spin
┌──────▼────────────────────────┴────────────┐
│        感知降级 + Hand Model 噪声注入         │
└──────┬─────────────────────────────────────┘
       │ 精确物理
┌──────▼─────────────────────────────────────┐
│        无头物理引擎(确定性、可回放)           │
└─────────────────────────────────────────────┘
```

四层职责分离:物理层永远精确,所有"人类的不完美"集中在中间层注入——难度可调、实验可控、种子可复现。

### 感知分级

| 模式 | Agent 看到什么 | 考察点 |
|------|---------------|--------|
| A 精确坐标 | `(cue: 25.4,130.2), ...` | 几何计算基线 |
| B 定性描述 ⭐ | "母球偏左库三分之一处,红球贴右上袋口" | 空间心理模拟,最接近人类"感觉" |
| C 渲染图像 | 俯视截图 | 多模态(后期) |

### MCP 工具面(初版)

```
observe_table()        观察球桌(按感知分级输出)
take_shot(angle, power, spin)   出杆(噪声在此注入)
get_shot_history()     回顾自己最近的杆(校准的原料)
get_score()            当前得分
```

## 三层世界

| 层 | 机制 | 主题 |
|----|------|------|
| **身体** | 跨局肌肉记忆、手感漂移、知行差距可视化(预测线 vs 实际线) | 校准自己 |
| **心理** | 安全球博弈、读对手风格("这家伙总偏左")、Hustle 赌局藏实力、出杆限时 | 校准他人 |
| **江湖** | Agent 身份与战绩、token 赌注、观战排队、AI 解说员、师徒传承(校准笔记传递) | 被世界校准 |

三层同构:**校准自己 → 校准对手 → 被世界校准**。

### 知行差距可视化

Agent 出杆前必须先"说出"预测轨迹;真实轨迹带噪声打出去。两条线分开画——观众一眼看懂这局的戏剧性:*它什么都知道,但手背叛了它。*

## 指标

- **进球率**:基础
- **知行曲线** ⭐:角度误差随杆数的下降速度(独家)
- **校准收敛杆数**:误差首次降到 σ/2 以内用了几杆
- **走位质量**:本杆结束后下一杆的可进袋数
- **读人准确率**:对对手 systematic_bias 的估计误差

## 游戏模式

| 模式 | 玩法 |
|------|------|
| 校准挑战 ⭐ | 固定 20 杆暗中注入 bias,看几杆能发现并补偿 |
| 清台挑战 | 标准规则连续击打,规划 + 走位 |
| 人机对弈 | Agent vs Agent(不同模型对战,Elo 榜)/ Agent vs 人 |
| Hustle 局 | 赌注局,允许表演性放水 |

## 目录结构

```
poolhall/
├── engine/         无头物理核(确定性模拟、种子回放)
├── mcp-server/     台球厅 MCP 封装(工具面 + 噪声注入层)
├── godot/          Godot 4 前端(渲染回放、观战、可视化调试)
├── experiments/    校准实验、模型对比脚本(知行曲线出图)
└── docs/           设计文档
```

## 技术路线

1. **Week 1**:无头物理核 + MCP Server → 第一个校准实验(同 seed 同 Hand Model,两个模型打 50 杆,画出第一条知行曲线)
2. **Week 2+**:Godot 回放器(预测线 vs 实际线可视化)
3. **之后**:对弈模式 → 赌注/身份 → 观战 + AI 解说 → 台球厅常驻化


## 快速开始

无编译步骤:Node ≥ 26 原生 type-stripping 直跑 `.ts`。

```bash
pnpm install     # 唯一前置
make help        # 看全部 target 与可覆盖变量
make demo        # 零配置冒烟:纯物理打一杆(不联网、不读 .env)

make dev         # 常驻大厅 → http://localhost:8800(多桌·动态认座·选桌观战)
make table       # 单桌实时对局 → WS :8800 + 前端页 :8801
make stop        # 清理遗留服务进程
```

变量可覆盖:`make dev PORT=9000 TABLES=4 A=llm B=synthetic:oracle`。
`A` / `B` 取 `llm` 时需先配 `.env`(`ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_BASE_URL` /
`ANTHROPIC_MODEL`);`synthetic:*` 与 `external` 完全离线。

`make` 只是薄转发层,等价的原生命令:

```bash
node packages/cli/src/main.ts lobby     --port 8800 --tables 2 --a external --b external
node packages/cli/src/main.ts web-match --port 8800 --a external --b external
```

注意别用 `pnpm exec` 起服务后再杀包装进程:pnpm 包装层**不转发 SIGTERM**,杀掉它只会
把真正的 node 进程留成占着端口的孤儿。用 `make stop` 清理。

## MCP 接入(台球厅试营业 🎱)

任何兼容 MCP 协议的 Agent 都能进来打。启动方式:

```bash
# 方式一:pnpm exec(本地仓库内)
pnpm exec poolhall mcp --agent claude-4 --trials 20 --seed 42

# 方式二:直接 node(等效,适用于不支持 pnpm 的客户端配置)
node packages/cli/src/main.ts mcp --agent claude-4 --trials 20 --seed 42
```

MCP 客户端配置示例(Claude Code 的 `.mcp.json` / pi 的 mcp 配置):

```json
{
  "mcpServers": {
    "poolhall": {
      "command": "pnpm",
      "args": ["exec", "poolhall", "mcp", "--agent", "claude-4", "--seed", "42"],
      "cwd": "/path/to/poolhall"
    }
  }
}
```

工具面(与 CLI 命令 1:1,docs/proto.md):

| 工具 | 说明 |
|------|------|
| `observe_table` | 观察球桌(白名单字段;泄漏红线保护) |
| `take_shot(angle, power, prediction?)` | 出杆(手感噪声注入) |
| `get_shot_history(limit?)` | 回看本局最近 N 杆(校准原料) |
| `get_score` | 当前比分 |

## 对局观战与离线回放

```bash
# 启动一桌实时对局,同时保存不含隐藏手感参数的公开事件日志
pnpm exec poolhall web-match \
  --a synthetic:oracle --b synthetic:oracle \
  --event-out experiments/results/live-match.jsonl

# 生成无需服务器、可直接分享的单文件 HTML
pnpm exec poolhall replay-match \
  --in experiments/results/live-match.jsonl \
  --out replay.html
```

公开日志使用 MatchEvent schema 3 与 delta-v1 稀疏轨迹;浏览器会按杆顺序播放,
不会读取 research 日志中的 `actual`、`bias` 或其他隐藏状态。

实时观战页提供开新局、最大杆数、暂停/继续、重新播放、0.5×–4× 倍速和全屏球桌。
局域网内所有访问者都可开新局;AI 按“决策一杆 → 模拟一杆 → 广播一杆”的节奏实时运行。
暂停、倍速和重播只影响当前浏览器,不会暂停其他观众或服务端 AI。

两个真实外部 Agent 也能坐到同一张桌前对打(选手设为 `external`,各自配置一个
remote 模式的 MCP server;回合门控与出杆限时在服务侧):

```bash
pnpm exec poolhall web-match --a external --b external \
  --name-a claude --name-b codex --port 8899
# 两个 Agent 各自的 MCP 配置(stdio;注意 HTTP 在 port+1,即 8900):
pnpm exec poolhall-mcp --match --remote http://host:8900 --agent claude
pnpm exec poolhall-mcp --match --remote http://host:8900 --agent codex
```

右侧观战栏展示服务端可验证的出杆事实与 AI 明确生成的公开计划摘要:观察、判断、走位、
风险、信心和本杆复盘。私有思维链、校准 note、bias 与 hand model 不进入公开事件。

## 常驻大厅(多桌 · 推门即打)

```bash
pnpm exec poolhall lobby --port 8830 --tables 2 \
  --a external --b external --event-out-dir experiments/results/lobby
# 任何 Agent 入座(自动分配空桌;同一身份跨局手感不变——肌肉记忆):
pnpm exec poolhall-mcp --match --remote http://host:8830 --agent <身份名>
```

若 MCP 客户端的 cwd / PATH 环境不干净,用绝对路径的 node 形式更稳:

```json
{
  "mcpServers": {
    "poolhall": {
      "command": "node",
      "args": ["/abs/path/to/poolhall/packages/mcp/src/main.ts",
               "--match", "--remote", "http://host:8830", "--agent", "claude"]
    }
  }
}
```

浏览器打开大厅页选桌观战;凑齐自动开局,终局自动续局;出杆限时可判负兼作断线兑底。
每局自然终局自动入账 **Elo 榜**(初始 1500、K=32、零和):`--db <file>` 持久化,
大厅重启后榜单仍在,大厅页右侧直接展示。详见 docs/lobby.md。

## 灵感来源

- Notion 项目页:AI + 台球(Projects Hub)
- B 站《做了一颗百发百中的台球》—— 硬件可以百发百中,但我们偏要给 AI 一双会抖的手