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 一双会抖的手
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues