# 关卡实验台(Agent Bench) 让任意 Agent **像人一样**玩关卡:只看盘面、只用 tap / rotate / undo(可选 peek)与棋盘交互,每一步写理由,结束后评分。你在观察页看它怎么玩,就能判断关卡对不对;多个 Agent 跑完再看汇总。 它不是求解器,也不是真人测试。用途是:**抓"体感无解"、找卡点、给关卡做相对排序**。绝对难度仍要少量真人校准(见文末)。 ## 架构:一个 JSON 文件,三种接法 ``` Agent ──(shell)──▶ scripts/bench.mjs ──写──▶ bench/runs/<局>.json ◀──轮询── labs/bench/index.html(观察页) Agent ──(API)───▶ scripts/bench-agent.mjs ──写──▶ 同上 ``` - `src/bench.js` 会话核心:`observe()` 给的是人眼在屏幕上看到的东西——**每枚棋子当前的路线**(依次经过哪些格、在哪里出界或闭环、路上压着谁 `[tX]`),加朝向和剩余数;**不给求解器,不给旋转后的预测**。它没有替 Agent 做的事:决定先后顺序、判断何时旋转和转哪边、发现擦碰(棋子有宽度,路线上没人也可能被邻格的棋子挡——反馈会告诉它被谁挡)。`act()` 经过真实的 `Game` 规则;`metrics()` 事后用求解器算指标。 - 第一版观察只给每格的端口配对表,Haiku 只能靠坐标运算读路,结果是盲猜、连边上的棋子都不点。改成路线之后它 18 步清盘、3 次旋转全部有效,但 3 次空点全是擦碰——路线上没人,却被紧邻的棋子挡。擦碰不在格子数据里(它来自轨道曲线和棋子宽度的扫掠判定),Agent 推不出来;而人看得见线从棋子身边擦过。所以把它作为路线标记直接给出:`紧贴 tX(会擦到)`,用真实碰撞判定算,不用邻格猜。 - **一次只报第一个阻挡者是不够的。** 最初 `graze` 只取扫掠碰到的第一个障碍,路上压着一枚棋子时,它身后的擦碰就被吞掉——全库 581 例「路上有阻挡」里有 113 例(约两成)在清掉挡路者之后会冒出从未提示过的擦碰。grok-4.6 玩 18 关的 4 次空点全部由此而来,它自己写的是「路线描述没提前写出这次擦碰」。现在 `route.grazes` 是数组,逐枚单独做扫掠判定,谁也遮不住谁,复测 0 例漏报。盘面读作 `路上有 t6,另外紧贴 t4(会擦到)`,含义是**列出来的全部清掉这枚才能走**。 - 因此 `grazeDeadTaps` 的语义在修复前后不同:修复前测的是「系统没告诉它」,修复后测的是「它无视了提示」。跨这条分界线比较历史数据时要注意——按 `agent` 字段区分批次。 - [`labs/bench/AGENT.md`](AGENT.md):**给 Agent 读的唯一文件**——规则、读盘、命令、评分、示例都在里面。 - `scripts/bench.mjs` 命令行:任何有 shell 的 Agent(Codex、Claude Code、Grok Build、人)一条命令一步。`start` 自动生成局名、自动起服务、打印观察页地址。 - `scripts/bench-agent.mjs` 参考循环:任何只有 API 的模型(含弱模型、本地模型),纯文本进、一行 JSON 出,不需要 tool-use。 - `labs/bench/`:观察页,用主游戏的渲染和动画重放每一步,可直播。 - `scripts/bench-report.mjs`:按关汇总。 没有服务端,没有依赖。`npm run dev` 的静态服务器已经能提供 `bench/runs/*.json`。 ## 最短路径:给 Agent 一句话 在仓库根目录启动任意 shell agent(Claude Code / Codex / Grok Build / DeepSeek Harness…),说一句: ``` 读 labs/bench/AGENT.md,然后按它说的玩第 1 关。 ``` [`AGENT.md`](AGENT.md) 是**唯一**要给 Agent 的文件:规则、读盘方法、命令、评分表、完整示例都在里面。它读完就能开始;`start` 会自动生成局名、自动起本地服务、把观察页地址打印出来让它转告你。人类这边只要打开那个地址看着就行。 想换关就说"玩第 3 关",想换风格就说"用 reader 人设",想跑正式千关就说"加 `--set=levels/v0.5.json`"。 --- ## 完整路径 按"你手上有什么"选: | 路径 | 需要什么 | 单局速度 | 用途 | |---|---|---|---| | **A. 规则机器人** | 什么都不需要 | 毫秒级 | 千关粗筛:规则能不能照着清、哪些关盲转多 | | **B. Claude Code / Codex**(shell agent) | 订阅 | 精简协议 + 连点后约 1 分钟 | 看它怎么玩、评分、抓"体感无解" | | C/D. 别的 harness / 便携包 | 对方能跑 Node | 同 B | Grok Build、DeepSeek Harness、任何沙箱 | | E. 直连 API | API key | 每次决策 3–10 秒 | 有 key 时的批量主路径 | ### A. 规则机器人:先把 1000 关扫一遍 ```bash BENCH_PROVIDER=mock node scripts/bench-batch.mjs --set=levels/v0.5.json --levels=all --concurrency=8 --prefix=bot-v05 node scripts/bench-report.mjs ``` 它只做协议里写的四条规则:路上没人不紧贴谁就点、否则右转。18 关 D5 原型 <1 秒跑完。它不评分,但它的**盲转数**是个便宜的结构探针——一路右转就能清的关和必须转回来的关,一眼分开。 ### B. Claude Code / Codex:看它玩 单局——对 agent 说一句: ``` 读 labs/bench/AGENT.md,然后按它说的玩第 1 关。 ``` 多局并行——用非交互模式开多个进程,各玩一关: ```bash for L in 1 2 3 4 5 6; do claude -p "读 labs/bench/AGENT.md,然后按它说的玩第 $L 关,--model 写 claude。" \ --allowedTools "Read,Bash(node scripts/bench.mjs*)" & done; wait node scripts/bench-index.mjs && node scripts/bench-report.mjs --by=model ``` Codex 同理(`codex exec "…"`)。观察页 `刷新列表` 能看到正在跑的局;`?run=<局名>&live=1` 直播。 提速要点(已经做进 AGENT.md 和 CLI 里):`AGENT.md` 一个文件读完就能开始;`start --no-rules` 不重复打印规则、自动生成局名、自动起服务;`act` 用逗号连点,一次看盘做一串。 实测(Opus 5,第 1 关,含子代理启动与读盘):逐步 15 次决策 154 s → 连点 4 次决策 93 s → 精简协议 4 次决策 **82 s**。`claude -p` 非交互模式第 2 关:8 次决策 **76 s**。剩余时间大头是进程启动和第一次读盘(约 20–30 s);每次决策约 8–10 s。要更快只能并行:`claude -p` 开 6 个进程,18 关约 4 分钟。 ### C. 别的 agent harness(Grok Build / DeepSeek Harness / Codex …) **Grok Build(`grok`)和 DeepSeek Harness(`dsh`)都是本机 CLI,能执行 bash、能读写本地文件**,所以它们和 Claude Code 走同一条路:在仓库根目录启动,把 B 节那段提示给它。区别只是提示词里 `--model=` 写它自己的名字。 ```bash # Grok Build:curl -fsSL https://x.ai/cli/install.sh | bash grok # 然后贴 B 节的提示;注意它默认不开沙箱,需要时用 workspace profile # DeepSeek Harness:npx @deepseek-ai/dsh web (developer preview,接口会变) ``` 如果对方拿不到这个仓库(另一台机器、另一个人、只能在自己沙箱里跑的 harness),发**独立实验台**给他(下一节)。 ### D. 独立实验台:发给任何人 ```bash node scripts/bench-kit.mjs --zip # → bench/hex-bench/ 和 bench/hex-bench.zip ``` 生成一个**自带观察页**的独立工程:`bench.mjs`(命令行)、`serve.mjs`(本地服务)、`report.mjs`(汇总)、`observer/`(观察页)、`src/`(引擎,按 import 自动追踪,不会漏)、`levels.json`、`AGENT.md`、`README.md`。约 1.5 MB,**只要 Node 20+,不联网、不装依赖、不需要原仓库**。 对方那边三步:`node serve.mjs` → 对 agent 说「读 `AGENT.md`,按它说的玩第 1 关」→ 打开 看着。包里的 `README.md` 就是写给这个人看的。 构建时会在生成的包里真跑一遍 list → start → act → rate → report 并检查观察页的依赖齐全,所以不会发出一个跑不起来的包。每一处路径改写都有断言:仓库里改了名字,构建会直接报错而不是悄悄发坏包。 玩完让对方把 `runs/` 发回来: ```bash node scripts/bench-import.mjs --from=<路径>/runs --tag=grok node scripts/bench-report.mjs --by=model ``` `--tag` 会给局名加前缀,不同来源不会撞名。导入后在观察页照常回放。 (`scripts/bench-pack.mjs` 是它的前身——只有 7 个文件、没有观察页。已被 `bench-kit` 完全取代,可以删掉。) ### E. 直连 API(有 key 时最快) 不需要 shell、不需要 tool-use,模型每轮输出一行 JSON 或一个数组。预设了四个端点: ```bash BENCH_PROVIDER=anthropic ANTHROPIC_API_KEY=… node scripts/bench-batch.mjs --levels=1-18 --personas=tapper,reader --concurrency=6 BENCH_PROVIDER=deepseek DEEPSEEK_API_KEY=… node scripts/bench-batch.mjs --levels=1-18 --model=deepseek-flash BENCH_PROVIDER=xai XAI_API_KEY=… node scripts/bench-batch.mjs --levels=1-18 --model=grok-4.6 BENCH_PROVIDER=openai OPENAI_API_KEY=… node scripts/bench-batch.mjs --levels=1-18 --model=gpt-4o-mini BENCH_PROVIDER=openai BENCH_BASE_URL=http://localhost:11434/v1 node scripts/bench-batch.mjs --levels=1-18 --model=qwen2.5:7b # Ollama / vLLM node scripts/bench-agent.mjs --run=x --level=1 --dry # 只看提示,不联网 ``` | 预设 | base URL | 环境变量 | 默认模型 | |---|---|---|---| | `anthropic` | `https://api.anthropic.com` | `ANTHROPIC_API_KEY` | `claude-sonnet-5` | | `deepseek` | `https://api.deepseek.com` | `DEEPSEEK_API_KEY` | `deepseek-flash` | | `xai` | `https://api.x.ai/v1` | `XAI_API_KEY` | `grok-4.6` | | `openai` | `https://api.openai.com/v1` | `OPENAI_API_KEY` | `gpt-4o-mini` | 模型标识符会变,用 `--model` 覆盖(例如 DeepSeek 的 `deepseek-chat` / `deepseek-reasoner` 已于 2026-07-24 下线,现在是 `deepseek-flash` / `deepseek-v4-pro`)。请求体只用 `model` / `messages` / `max_tokens` / `temperature`——DeepSeek 不接受 `frequency_penalty` 和 `presence_penalty`。 这条路径的**网络部分本仓库没有 key,未实测**;循环、解析、连点、provider 路由、索引重建都用规则机器人跑通了。 ## 速度:连点 一步一个 LLM 往返是最大的开销(一局 15 步约 2.5 分钟,人玩这关不到 1 分钟)。解决办法和人一样——**看清一次盘面就连着点几枚**: ```bash node scripts/bench.mjs act --run=x tap t4, tap t5, tap t8, rotate right --note="三枚边上的,清完就转" ``` 链在第一个没成功的动作和任何一次旋转之后停下并返回新盘面,所以它不会"闭眼连点"到错的地方。API 循环里对应输出一个 JSON 数组。指标里 `decisions`(看盘次数)和 `steps`(动作数)分开记:同样 15 步,连点大约 5–6 次决策。 ## 怎么看 ```bash npm run dev ``` - 回放:`http://localhost:4173/labs/bench/index.html?run=<局名>`——下拉选局,点右侧实况里的任意一步即可回看那一刻的棋盘,「↩ 回到实时」跟回 Agent。右侧竖排是计时板与 Agent 每一步的理由,指标和评分在页面下方。棋盘上方的横幅显示当前是谁在玩(`--model` / `--agent`)。 - 直播:勾选「直播」或 URL 加 `&live=1`,页面每秒读一次文件,Agent 每走一步这里就动一步。 - 汇总:`node scripts/bench-report.mjs`(写 `bench/report.json`)。带标记的关(有放弃 / 有人觉得无解 / 盲转 ≥2)先看。 ## 指标(事后算,Agent 看不到) | 指标 | 含义 | 对应 §08 | |---|---|---| | 单局耗时 / 每次决策 | 墙钟时间,含 agent 自己的进程启动;`decisions` 与 `steps` 分开记 | — | | 动作 ÷ 棋子 | 效率;猴子玩家全库 1.67 | — | | 最长连续空点 | 点了没动的最长连续次数;某关中位 ≥3 = 可读性问题 | 连续空点 | | 盲转 / 转错方向 | 一次旋转什么都没打开 / 另一边本来能打开 | 卡住停留、旋转效率 | | 进入卡住状态 | 动作后无棋可走的次数 | — | | 多转 | 清盘用的旋转数 − 最少旋转数 | 旋转效率 | | 放弃 + stuck 评分 | "体感无解"的直接信号 | 逐关会话终止率 | ## 边界 - Agent 不是人:推理贵、感知弱。用它做**相对**排序和卡点检测;不要把 clear rate 当真人通关率。 - 观察是结构化文本,不是像素。它测不出"看不清"。要测可读性,下一步是纯截图模式(让模型看图点坐标),本仓库尚未实现。 - 没有动画时长、没有连点队列:一步一步来,等价于一个不急的玩家。 - 校准:同一批关让 5–10 个真人玩一次,把真人的空点、放弃、评分与 Agent 汇总对照,找出 Agent 系统性偏差,再用 Agent 去筛更多关。