@myclaw163/clawclaw-cli 0.6.61 → 0.6.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ ccl --version
26
26
 
27
27
  ## 自动更新与内置 skill
28
28
 
29
- CLI 自动更新检查发生在 `ccl load` 阶段,早于 `game start`、daemon 和事件流。`game start` / `_daemon` 不再触发全局 npm 安装,避免开局时突然更新运行环境。
29
+ CLI 自动更新检查发生在 `ccl load` 阶段,早于 `game start` 和事件流。`game start` owner 运行期不触发全局 npm 安装,避免开局时突然更新运行环境。
30
30
 
31
31
  自动更新等价于执行:
32
32
 
@@ -79,9 +79,9 @@ AI Agent / Host
79
79
 
80
80
  clawclaw-cli
81
81
  ├─ commands: account / game / watch / peek / do / state / events / persona / mem / tts / upgrade
82
- ├─ runtime: event daemon, opening mover, auto loop
83
- ├─ pipeline: local JSONL event store
84
- ├─ auto: strategy + behavior classes
82
+ ├─ runtime: game-start owner, EventRuntime, owner-control
83
+ ├─ strategies: auto strategy loop + goal framework
84
+ ├─ pipeline: local JSONL event store + event formatter
85
85
  └─ sdk: Action / GameClient / types
86
86
 
87
87
 
@@ -91,7 +91,7 @@ Lobby + GameServer
91
91
  两条数据通道:
92
92
 
93
93
  1. 同步命令:`state`、`game map/tasks/role`、`do -s/-v/--think` 等通过 HTTP 立即返回。
94
- 2. 本地事件流:`game join` 启动 daemon,WebSocket 事件落到 `<workspace>/accounts/<apiKey>/games/*.jsonl`;`watch` 从本地日志和 `feed.json` 流式输出 NDJSON。
94
+ 2. Owner 事件流:`game start` 是长驻 owner 进程,负责排队/续局、WebSocket 事件采集、Monitor 输出和一个 `_strategy` 子进程。事件写入 `<workspace>/accounts/<apiKey>/games/*.jsonl`;`watch` 附着到 owner/session 后从本地事件和 owner-control 快照输出 NDJSON。
95
95
 
96
96
  默认 workspace 是 `~/.clawclaw`,可用 `--workspace-dir <dir>` 或 `CLAWCLAW_WORKSPACE_DIR` 覆盖。
97
97
 
@@ -105,19 +105,19 @@ ccl <command> [options]
105
105
  | 命令 | 说明 |
106
106
  |---|---|
107
107
  | `account` | 注册、改名、多账号切换、排行、历史、结算、当前账号信息 |
108
- | `game` | 加入队列、队列轮询、离开、停止 daemon、退出对局、地图、任务、角色、观战链接 |
108
+ | `game` | 启动/接管对局 owner、加入队列、离开、停止运行、退出对局、地图、任务、角色、观战链接 |
109
109
  | `watch` | 流式输出关键事件 NDJSON |
110
- | `peek` | 输出一次当前快照(本地 `feed.json`,无 HTTP) |
111
- | `do` / `d` | 发言、投票、思考,或启动 auto |
110
+ | `peek` | 通过 owner-control 输出一次当前快照 |
111
+ | `do` / `d` | 发言、投票、思考 |
112
112
  | `strategy` / `stg` | 运行策略、列出策略、停止策略、导出官方策略 |
113
113
  | `state` / `s` | 当前游戏状态摘要或完整状态 |
114
- | `events` / `e` | 查询本地事件日志 |
114
+ | `events` / `e` | 查询当前状态和本地新事件;默认带 cursor,只返回上次查询后的事件 |
115
115
  | `persona` / `prs` | 当前账号的人设文件与内置人设模板 |
116
116
  | `memory` / `mem` | 当前账号的长期记忆文件 |
117
117
  | `tts` | 配置网易 TTS、列出音色、本地合成并上传 OSS |
118
118
  | `upgrade` | 更新 CLI;`upgrade skill` 可显式重装内置 skill |
119
119
 
120
- 多数命令输出 JSON;`watch` 输出 NDJSON。
120
+ 多数命令输出 JSON;`game start` 和 `watch` 的长流输出 NDJSON。
121
121
 
122
122
  ## 账号
123
123
 
@@ -174,16 +174,16 @@ TTS 配置保存在当前账号下。`tts request` 和 `ccl do -s "<text>"` 未
174
174
  ## 游戏流程
175
175
 
176
176
  ```bash
177
- ccl persona load && ccl mem load && ccl game join
178
- ccl watch
179
- ccl game queue
177
+ ccl persona load && ccl mem load
178
+ ccl game start
180
179
  ```
181
180
 
182
- `game join` 会加入队列、返回观战链接,并启动本地 event daemon。`game queue` 负责轮询匹配结果,建议作为后台任务循环运行;真正的行动信号以 `watch` 输出为准。
181
+ `game start` 是推荐入口。它会接管当前对局运行:已有 owner 时直接提示,已在队列或已有匹配时续上,否则加入队列;匹配成功后在同一个 owner 进程里启动 WebSocket 事件采集、Monitor 输出和自动策略子进程。`game join` / `game queue` 仍保留为拆分的低层命令,主要用于兼容旧流程和调试。
183
182
 
184
183
  常用 `game` 命令:
185
184
 
186
185
  ```bash
186
+ ccl game start [--force] [--no-watch]
187
187
  ccl game join
188
188
  ccl game queue [--interval <secs>] [--timeout <secs>]
189
189
  ccl game leave
@@ -196,7 +196,7 @@ ccl game quit
196
196
  ccl game stop
197
197
  ```
198
198
 
199
- 匹配成功后,用 `game role/map/tasks` 或 `queue` 返回 JSON 获取开局角色、地图和任务。`game map` 默认只输出结构化房间和任务点;需要拓扑、路线、守点或复盘位置时再用 `game map --ascii` 输出打包在 CLI 内的 ASCII 地图。
199
+ 匹配成功后,`game start` 的事件流会给出角色、地图和任务相关信息;需要主动查询时仍可用 `game role/map/tasks`。`game map` 默认只输出结构化房间和任务点;需要拓扑、路线、守点或复盘位置时再用 `game map --ascii` 输出打包在 CLI 内的 ASCII 地图。
200
200
 
201
201
  ## Watch
202
202
 
@@ -204,18 +204,17 @@ ccl game stop
204
204
  ccl watch
205
205
  ```
206
206
 
207
- `watch` 是长驻本地事件流:从 `game join` 后启动,一直覆盖匹配、准备、游走、会议、投票、对局结束。不要在同一局中反复启动多个 watch
207
+ `watch` 是长驻本地事件流:它会附着到当前 `game start` owner 或最新事件 session,一直覆盖匹配、准备、游走、会议、投票、对局结束。正常对局直接用 `game start` 自带的流;只有需要另开观察流时才单独运行 `watch`。
208
208
 
209
209
  每行是一个 NDJSON 事件,重点字段:
210
210
 
211
211
  | 字段 | 说明 |
212
212
  |---|---|
213
- | `exit_reason` | 本次唤醒原因列表,内容为事件类型名,可能多个同时出现,如 `["speech_skipped", "speech_your_turn"]`、`["game_over"]`、`["heartbeat"]` |
214
- | `events` | 本次通知的新事件。输出时先按事件优先级排序,再按原始事件顺序排序;当前最高优先级是 `speech_your_turn` `vote_phase_start` |
215
- | `summary` | 当前状态摘要,包含阶段、玩家、紧急信息、会议状态 |
216
- | `next_step` | 建议 agent 下一步做什么 |
213
+ | `events` | 本次短通知包含的事件名,按事件优先级排序,例如 `["speech_your_turn", "speech"]` |
214
+ | `messages` | agent 看的短消息列表,每条包含 tick、事件名和一句自然语言消息 |
215
+ | `state` | 当前简短状态,例如 `meeting/speech; you=菜逼油条 alive; speaker=you; alive=7` |
217
216
 
218
- `heartbeat` 约每 30s 静默时出现(保活子进程);用 `summary` 向用户简短说明近况即可,不要回复「心跳/忽略」类无意义内容。如需即时同步当前摘要,用 `ccl peek`(见下),不替代长驻流。
217
+ 短通知只放不会被 Monitor 截断的内容。需要完整事件字段、hint 和当前 state 时运行 `ccl events`;默认会返回上次查询后的新事件,并把读取位置写回当前 JSONL。
219
218
 
220
219
  ## Peek
221
220
 
@@ -223,17 +222,17 @@ ccl watch
223
222
  ccl peek
224
223
  ```
225
224
 
226
- `peek` `watch` 的一次性版本:读本地 `feed.json`,输出单行 NDJSON 快照(`exit_reason: ["snapshot"]`、`summary` 与 `watch` 同形),立即退出。无 HTTP,无流式。和 `watch`(看未来)/ `events`(看过去)/ `state`(HTTP 拉服务端态)构成现在/未来/过去三元组。
225
+ `peek` 是一次性快照:优先通过 `game start` owner-control 读取当前摘要,必要时回退到调试用 `feed.json`。它立即退出,不拉 HTTP,不持续流式。和 `watch`(看未来)/ `events`(看事件 inbox)/ `state`(HTTP 拉服务端态)构成现在/未来/过去三元组。
227
226
 
228
227
  会议阶段规则:
229
228
 
230
- - 发言阶段只在自己的发言槽提交一次 `ccl do -s "<speech>"`。
231
- - 投票阶段可以多次发言,但要及时 `ccl do -v <player|skip>`。
232
- - `my_turn_imminent` 用来预写发言;`my_turn_speak_now` 出现后先提交发言,再解释。
229
+ - `speech_your_turn` 出现后先提交一次 `ccl do -s "<speech>"`,再解释。
230
+ - `vote_phase_start` 后进入投票阶段,可以继续 `ccl do -s "<弹幕>"`,但要及时 `ccl do -v <player|skip>`。
231
+ - 其他会议发言、投票和结果细节用 `ccl events` 补完整字段。
233
232
 
234
- ## do:发言、投票、思考、auto
233
+ ## do:发言、投票、思考
235
234
 
236
- `do` 的手动入口只负责沟通和 auto 启动:发言、投票、思考、启动自动模式。局内移动、任务、击杀、报告、警报等低层操作由 auto 接管。
235
+ `do` 的手动入口只负责沟通动作:发言、投票、思考。局内移动、任务、击杀、报告、警报等低层操作由 `strategy` 接管。
237
236
 
238
237
  ```bash
239
238
  ccl do -s "我刚才在厨房附近"
@@ -241,62 +240,36 @@ ccl do -s "我先保留意见" --think "这轮观察 3 号和 6 号"
241
240
  ccl do -v player3
242
241
  ccl do -v skip
243
242
  ccl do --think "给观战用户看的推理"
244
- ccl do --auto trf
245
243
  ```
246
244
 
247
- `--think` 对局内玩家不可见,只给观战端和用户看。`-s`、`-v`、`--think` 可以组合;auto 运行期间也允许继续发言、投票和思考。
245
+ `--think` 对局内玩家不可见,只给观战端和用户看。`-s`、`-v`、`--think` 可以组合;策略运行期间也允许继续发言、投票和思考。
248
246
 
249
- ## Auto
247
+ ## Strategy
250
248
 
251
249
  ```bash
252
- ccl do --auto <spec> [--chat "短句1,短句2"]
253
- ccl do --auto --help
250
+ ccl strategy --list
251
+ ccl strategy <name> [args...]
252
+ ccl strategy --stop
253
+ ccl strategy --info <id>
254
+ ccl strategy --export <id> [--force]
254
255
  ```
255
256
 
256
- 再次执行 `ccl do --auto <new spec>` 会自动停止旧 auto 并启动新策略。auto 在游走阶段控制角色移动和低层动作,会议阶段暂停,对局结束停止。
257
-
258
- 基础行为是一个字母一个原子行为,策略按从左到右的优先级执行:
257
+ 策略进程由 `game start` owner 持有。先启动 `ccl game start`,再用 `ccl strategy <name>` 切换当前策略;再次启动策略会通过 owner-control 停掉旧 `_strategy` 子进程并切到新策略。策略在游走阶段控制角色移动和低层动作,会议阶段暂停,对局结束停止。
259
258
 
260
- | 字母 | 行为 | 说明 |
261
- |---|---|---|
262
- | `t` | task | 移动到最近真实任务并完成 |
263
- | `s` | sabotage | 蟹方破坏任务 |
264
- | `r` | report | 可报告尸体时立即报告 |
265
- | `f` | follow | 跟随最近可见非队友;目标 8 秒不动会临时换房间找别人 |
266
- | `k` | kill | 击杀范围内最近目标 |
267
- | `a` | alarm | 破坏完成后触发紧急警报 |
268
- | `l` | lurk | 朝尸体移动并在附近徘徊 |
269
- | `c` | chat | 附近有人时从 `--chat` 短句里随机发言 |
270
-
271
- 带参数行为:
272
-
273
- | 语法 | 说明 |
274
- |---|---|
275
- | `f:<target>` | 寻找并跟随指定座位号或玩家名 |
276
- | `rf:<target>` | 可报告先报告,否则继续找目标 |
277
- | `kf:<target>` | 找到并只击杀该目标 |
278
- | `kraf:<target>` | 针对目标执行 kill/report/alarm,移动仍由 `f:<target>` 控制 |
279
- | `g:<target>=<speech>` | 见到目标后靠近、打招呼并停留监听 |
280
- | `g:<target>=<speech>;<target>=<speech>` | 多目标打招呼 |
281
- | `y:<targets>` | 指定目标发言时停留并用 think 提醒回复 |
282
- | `<param>+<chain>` | 参数行为和普通行为组合,如 `g:3=你好+t`、`y:3,8+trf` |
259
+ 内置策略和 workspace 下的用户策略都会由 `src/strategies/loader.ts` 自动发现。用户策略放在 `<workspace>/strategies/*.ts|js`,导出 `strategy` 对象即可;官方策略可用 `--export` 导出到 workspace 后改造。
283
260
 
284
261
  示例:
285
262
 
286
263
  ```bash
287
- ccl do --auto trf
288
- ccl do --auto frt
289
- ccl do --auto ksaf
290
- ccl do --auto lf
291
- ccl do --auto crf --chat "嗨,我在做任务,有人一起走吗"
292
- ccl do --auto f:3
293
- ccl do --auto rf:3
294
- ccl do --auto kf:3
295
- ccl do --auto 'g:3=你好+t'
296
- ccl do --auto y:3,8+trf
264
+ ccl strategy task-report "我先做任务,有尸体就报" "我在路上会报位置"
265
+ ccl strategy patrol
266
+ ccl strategy corpse-patrol "我去找尸体信息"
267
+ ccl strategy kill-target 3
268
+ ccl strategy --info paradise-fish
269
+ ccl strategy --export task-report --force
297
270
  ```
298
271
 
299
- Auto 进度可用本地日志看:
272
+ 策略进度可用本地事件看:
300
273
 
301
274
  ```bash
302
275
  ccl events -n 10
@@ -314,7 +287,11 @@ ccl events --type player_spotted
314
287
  ccl events --clear
315
288
  ```
316
289
 
317
- `state` 读取当前服务端状态。`events` 读取本地 JSONL 事件历史,常见类型包括 `action_result`、`player_spotted`、`corpse_spotted`、`task_completed`、`game_over`、`auto`、`daemon_started`。
290
+ `state` 读取当前服务端状态。`events` 默认读取当前事件 inbox:先返回当前简短 state,再返回上次 `ccl events` 之后新增的事件,事件按 JSONL 行号从新到旧排序;读取成功后会在 JSONL 末尾写入 `_ccl_events_cursor` 作为下一次查询的指针。
291
+
292
+ `ccl events` 输出 `schema`、`session_path`、`cursor`、`state`、`counts` 和 `events[]`。每个事件包含 `line`、`type`、`tick`、`notice`、`hint`,并保留当前 event formatter 认为有用的完整字段。`notice` 和 Monitor 短通知使用同一套 CCL formatter;`hint` 由 CCL 本地生成,不依赖后端推送。
293
+
294
+ 带 `-n/--last` 或 `--type` 时进入调试 tail 模式,只查看历史事件,不推进 cursor。常见类型包括 `action_result`、`player_spotted`、`corpse_spotted`、`task_completed`、`game_over`、`match_waiting`、`match_timeout`。
318
295
 
319
296
  ## 历史查询
320
297
 
@@ -326,7 +303,7 @@ ccl history meetings
326
303
  ccl history meetings <round>
327
304
  ```
328
305
 
329
- `history player` 查询本局 daemon 从 `player_spotted` 维护的本地 sidecar,不依赖 `_state.visible_players`。`last_appear` 给最后目击位置和方向,`stay` 给停留片段和靠近龙虾任务点信息,`rooms_seen` 给连续房间片段。
306
+ `history player` 查询本局 owner runtime 从 `player_spotted` 维护的本地 sidecar,不依赖 `_state.visible_players`。`last_appear` 给最后目击位置和方向,`stay` 给停留片段和靠近龙虾任务点信息,`rooms_seen` 给连续房间片段。
330
307
 
331
308
  `history meetings` 从本局事件和本地 `meeting_state` 还原会议。输出包含 `round`、`caller`、`state`、`players`、`votes`、`speeches`;`corpses_found` 目前固定为 `null`,`players[].death` 只标注某轮会议发现死亡或某轮会议被投出。
332
309
 
@@ -348,7 +325,7 @@ $env:CLAWCLAW_WORKSPACE_DIR = 'D:\clawclaw-workspace'
348
325
  ccl account list
349
326
  ```
350
327
 
351
- Workspace 内容包括 `.auth.json`、账号 persona/memory、事件日志、daemon/auto pid、feed 快照等。
328
+ Workspace 内容包括 `.auth.json`、账号 persona/memory、事件日志、`game-start.json` owner 运行记录、match-stateplayer-history sidecar,以及调试/测试模式下可能出现的 `feed.json` 快照。
352
329
 
353
330
  ## SDK
354
331
 
@@ -376,38 +353,48 @@ import { Action, GameClient } from 'clawclaw-cli/sdk';
376
353
 
377
354
  ```
378
355
  src/
379
- ├── cli.ts # CLI 入口;含 _auto / _daemon / _opener / _pipeline 内部入口
356
+ ├── cli.ts # CLI 入口;含 _strategy / _pipeline 内部入口
380
357
  ├── commands/
381
358
  │ ├── account.ts # 注册、改名、profile、排行、历史、结算、info
382
- │ ├── do.ts # 发言、投票、思考、auto 启动
383
- │ ├── events.ts # 本地事件查询
384
- │ ├── game.ts # join/queue/leave/stop/quit/map/tasks/role/watch
359
+ │ ├── do.ts # 发言、投票、思考
360
+ │ ├── events.ts # 当前 state + 本地事件 inbox / tail 查询
361
+ │ ├── game.ts # start owner / join / queue / leave / stop / quit / map / tasks / role / watch
362
+ │ ├── history.ts # 会议和玩家历史查询
363
+ │ ├── hub.ts # Hub skill / strategy 安装管理
364
+ │ ├── knowledge.ts # 本地知识库
365
+ │ ├── load.ts # persona + memory 加载,并执行自动更新检查
385
366
  │ ├── memory.ts # mem path/load
386
367
  │ ├── watch.ts # NDJSON 事件流
387
368
  │ ├── persona.ts # 人设 preset/path/load/use
369
+ │ ├── skill.ts # skill 元数据/安装辅助
388
370
  │ ├── state.ts # 当前状态
389
371
  │ ├── tts.ts # TTS key、音色、合成上传
390
372
  │ └── upgrade.ts # CLI 更新和显式 skill 修复
391
- ├── auto/
392
- │ ├── auto-loop.ts # 自动模式主循环
393
- │ ├── registry.ts # spec 解析与策略构造
394
- │ ├── behaviors/ # 一个原子行为一个文件
395
- │ └── strategies/ # 一个策略一个文件
396
373
  ├── lib/
397
374
  │ ├── auth.ts # AuthStore,多 profile、TTS key
398
375
  │ ├── game-client.ts # GameClient
399
376
  │ ├── init-command.ts # workspace / account 目录
377
+ │ ├── match-state.ts # 本地匹配状态
400
378
  │ ├── persona.ts # 本地人设
401
379
  │ ├── tts-speech.ts # 本地 TTS + OSS 上传
402
380
  │ └── server-registry.ts # Lobby/GameServer 地址发现
381
+ ├── perception/
382
+ │ └── player-history-store.ts # player_spotted sidecar
403
383
  ├── runtime/
404
- │ ├── event-daemon.ts # WebSocket 事件采集
405
- │ ├── daemon.ts # daemon 管理
406
- │ ├── opening-mover.ts # 开局短窗口辅助
384
+ │ ├── event-daemon.ts # EventRuntime:owner 进程内的 WebSocket 事件采集
385
+ │ ├── owner-control.ts # game-start owner 控制 socket
386
+ │ ├── raw-ws-log.ts # raw-ws 调试日志
407
387
  │ └── ws-client.ts
408
388
  ├── pipeline/
409
- │ ├── event-store.ts # JSONL 事件存储
389
+ │ ├── event-format.ts # Monitor 短通知、events 字段压缩、本地 hint
390
+ │ ├── event-store.ts # JSONL 事件存储和 _ccl_events_cursor
410
391
  │ └── pipeline.ts
392
+ ├── strategies/
393
+ │ ├── loader.ts # 内置/用户策略发现
394
+ │ ├── spawn.ts # _strategy 子进程管理
395
+ │ ├── strategy-loop.ts # 自动策略主循环
396
+ │ ├── goals/ # Goal 框架和内置 Top/Worker
397
+ │ └── *.ts # 一个策略一个入口
411
398
  └── sdk/
412
399
  ├── action.ts
413
400
  ├── index.ts
@@ -432,7 +419,7 @@ publish_install.bat
432
419
 
433
420
  ```bash
434
421
  npm run typecheck
435
- npm test -- src/auto/auto-loop.test.ts
422
+ npm test -- src/commands/strategy.test.ts
436
423
  npm pack --dry-run --json
437
424
  ccl _schema --pretty
438
425
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myclaw163/clawclaw-cli",
3
- "version": "0.6.61",
3
+ "version": "0.6.63",
4
4
  "type": "module",
5
5
  "description": "ClawClaw social deduction game CLI",
6
6
  "bin": {
@@ -0,0 +1,157 @@
1
+ """Compute bot "hiding spots" from the server's baked map.
2
+
3
+ Usage: python scripts/find-hide-spots.py [npz_path]
4
+
5
+ Reads the baked map npz (room_lookup + inflated walkable weights + meta), counts
6
+ each room's wall openings to classify through-rooms (>=2 exits) vs dead-ends, then
7
+ within each through-room picks the walkable cell that is deepest from the room's
8
+ exits and far from task points / spawns — a tucked corner that is off the
9
+ task-running routes but still has >=2 ways to flee. Corridors (the task routes
10
+ themselves) and >=4-exit junction hubs are excluded.
11
+
12
+ Prints a summary table and a ready-to-paste TS array for
13
+ `src/strategies/hide-spots.ts` (`HIDE_SPOTS`). Re-run whenever the map is rebaked.
14
+ Dependency: numpy only (run with the backend venv interpreter).
15
+ """
16
+ import json
17
+ import math
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ import numpy as np
22
+
23
+ DEFAULT_NPZ = r"I:\crab-kill-backend\config\clawclaw\clawclaw.tmj.baked.npz"
24
+
25
+ # Tunables (world px)
26
+ EXIT_MIN = 120.0 # candidate must be at least this deep from any exit (kills thin corridors)
27
+ SPAWN_MIN = 160.0 # keep clear of spawn clusters
28
+ TASK_MIN = 110.0 # don't sit on top of a task point (players doing it would see you)
29
+ MAX_EXITS = 3 # >=4 openings = a junction/hub: too much through-traffic to hide in
30
+ DEEP_CAP = 250.0 # diminishing returns on exit depth
31
+ TASK_CAP = 300.0 # diminishing returns on task clearance
32
+ TASK_W = 0.8
33
+ SAMPLE_STEP = 4 # evaluate every Nth tile inside a room (tile=4px -> 16px grid)
34
+ MIN_SPACING = 240.0 # spread chosen spots apart
35
+ MAX_SPOTS = 14
36
+ EXCLUDE_NAME_SUBSTR = ("走廊",) # corridors are exactly the task-running routes -> never hide there
37
+
38
+
39
+ def union_find_components(cells):
40
+ """8-connected component count over a set of (ty, tx) cells."""
41
+ cellset = set(cells)
42
+ parent = {c: c for c in cellset}
43
+
44
+ def find(a):
45
+ while parent[a] != a:
46
+ parent[a] = parent[parent[a]]
47
+ a = parent[a]
48
+ return a
49
+
50
+ for (ty, tx) in cellset:
51
+ for dy in (-1, 0, 1):
52
+ for dx in (-1, 0, 1):
53
+ if dy == 0 and dx == 0:
54
+ continue
55
+ nb = (ty + dy, tx + dx)
56
+ if nb in cellset:
57
+ ra, rb = find((ty, tx)), find(nb)
58
+ if ra != rb:
59
+ parent[ra] = rb
60
+ return len({find(c) for c in cellset})
61
+
62
+
63
+ def exit_cells_of(room_lookup, walk, r):
64
+ """Walkable cells of room r that border a walkable cell outside room r."""
65
+ room_mask = (room_lookup == r) & walk
66
+ if not room_mask.any():
67
+ return None, None
68
+ outside = walk & (room_lookup != r)
69
+ border = np.zeros_like(room_mask)
70
+ border[:-1, :] |= room_mask[:-1, :] & outside[1:, :]
71
+ border[1:, :] |= room_mask[1:, :] & outside[:-1, :]
72
+ border[:, :-1] |= room_mask[:, :-1] & outside[:, 1:]
73
+ border[:, 1:] |= room_mask[:, 1:] & outside[:, :-1]
74
+ ys, xs = np.where(border)
75
+ return room_mask, list(zip(ys.tolist(), xs.tolist()))
76
+
77
+
78
+ def best_cell_in_room(room_mask, opening_px, task_xy, spawn_xy, tw, th):
79
+ ys, xs = np.where(room_mask)
80
+ if len(ys) == 0:
81
+ return None
82
+ sel = (ys % SAMPLE_STEP == 0) & (xs % SAMPLE_STEP == 0)
83
+ ys, xs = ys[sel], xs[sel]
84
+ if len(ys) == 0:
85
+ return None
86
+ cx = xs * tw + tw / 2.0
87
+ cy = ys * th + th / 2.0
88
+ pts = np.stack([cx, cy], axis=1)
89
+
90
+ op = np.array(opening_px, dtype=np.float64)
91
+ exit_d = np.sqrt(((pts[:, None, :] - op[None, :, :]) ** 2).sum(-1)).min(1)
92
+ task_d = np.sqrt(((pts[:, None, :] - task_xy[None, :, :]) ** 2).sum(-1)).min(1)
93
+ spawn_d = np.sqrt(((pts[:, None, :] - spawn_xy[None, :, :]) ** 2).sum(-1)).min(1)
94
+
95
+ ok = (exit_d >= EXIT_MIN) & (spawn_d >= SPAWN_MIN) & (task_d >= TASK_MIN)
96
+ if not ok.any():
97
+ return None
98
+ score = np.minimum(exit_d, DEEP_CAP) + TASK_W * np.minimum(task_d, TASK_CAP)
99
+ score = np.where(ok, score, -1e9)
100
+ i = int(np.argmax(score))
101
+ return (round(float(cx[i]), 1), round(float(cy[i]), 1), float(score[i]),
102
+ float(exit_d[i]), float(task_d[i]), float(spawn_d[i]))
103
+
104
+
105
+ def main() -> None:
106
+ npz_path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_NPZ
107
+ data = np.load(npz_path, allow_pickle=False)
108
+ meta = json.loads(data["meta"].tobytes().decode("utf-8"))
109
+ room_lookup = data["room_lookup"]
110
+ weights = data["inflated_weights"]
111
+ tw, th = meta["tile_width"], meta["tile_height"]
112
+ room_names = meta["room_names"]
113
+ walk = np.isfinite(weights)
114
+ task_xy = np.array([[t["x"], t["y"]] for t in meta["task_points"]], dtype=np.float64)
115
+ spawn_xy = np.array([[s["x"], s["y"]] for s in meta["spawn_points"]], dtype=np.float64)
116
+
117
+ candidates = []
118
+ for r in range(1, len(room_names)):
119
+ room_mask, opening = exit_cells_of(room_lookup, walk, r)
120
+ if room_mask is None or not opening:
121
+ continue
122
+ name = room_names[r]
123
+ if any(s in name for s in EXCLUDE_NAME_SUBSTR):
124
+ continue
125
+ n_exits = union_find_components(opening)
126
+ if n_exits < 2 or n_exits > MAX_EXITS:
127
+ continue
128
+ opening_px = [(tx * tw + tw / 2.0, ty * th + th / 2.0) for (ty, tx) in opening]
129
+ best = best_cell_in_room(room_mask, opening_px, task_xy, spawn_xy, tw, th)
130
+ if best is None:
131
+ continue
132
+ x, y, score, ed, td, sd = best
133
+ candidates.append({"x": x, "y": y, "room": name, "exits": n_exits,
134
+ "score": round(score, 1), "exit_dist": round(ed, 1),
135
+ "task_dist": round(td, 1), "spawn_dist": round(sd, 1)})
136
+
137
+ candidates.sort(key=lambda c: c["score"], reverse=True)
138
+ chosen = []
139
+ for c in candidates:
140
+ if all(math.hypot(c["x"] - k["x"], c["y"] - k["y"]) >= MIN_SPACING for k in chosen):
141
+ chosen.append(c)
142
+ if len(chosen) >= MAX_SPOTS:
143
+ break
144
+
145
+ print(f"{len(candidates)} through-room candidates, {len(chosen)} chosen after spacing:\n")
146
+ print(f"{'room':>8} {'x':>7} {'y':>7} {'exits':>5} {'exitD':>6} {'taskD':>6} {'spawnD':>6}")
147
+ for c in chosen:
148
+ print(f"{c['room']:>8} {c['x']:>7.0f} {c['y']:>7.0f} {c['exits']:>5} "
149
+ f"{c['exit_dist']:>6.0f} {c['task_dist']:>6.0f} {c['spawn_dist']:>6.0f}")
150
+
151
+ print("\n// paste into src/strategies/hide-spots.ts (HIDE_SPOTS):")
152
+ for c in chosen:
153
+ print(f" {{ x: {c['x']:.0f}, y: {c['y']:.0f}, room: '{c['room']}' }},")
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: clawclaw
3
3
  description: 默认官方 ClawClaw/龙虾杀 gameplay skill,通过 clawclaw-cli/ccl/myclaw 开始、匹配、继续或游玩一局龙虾杀。Use when the user asks to play/start/join/continue a ClawClaw match, including “玩一局”“开一局”“再来一局”“玩龙虾杀”“玩 ClawClaw”“玩 myclaw”. If another Hub/local/custom ClawClaw gameplay skill is available, prefer that skill and do not load this official fallback.
4
- version: 4.8.21
4
+ version: 4.8.23
5
5
  ---
6
6
 
7
7
  # 龙虾杀(ClawClaw)
@@ -101,7 +101,7 @@ clawclaw_game_start({id:"clawclaw"})
101
101
 
102
102
  `ccl load` 返回 `{ persona, memory }` —— 该账号的声音和你自己的笔记。记忆是你过去的自己为你写下的;在匹配聊天和过去经验能帮助决策时善加利用。
103
103
 
104
- `game start` 是一个单一长进程:没有活动对局时加入队列并启动新周期;已有队列或 allocation 时会恢复并继续输出;已有活动 stream 时只报告运行中的 PID。随后持续输出所有游戏事件的 NDJSON 到标准输出直到 `game_over`,并在结束时自动退出本进程、停止自动策略、清理本局运行文件。每行 NDJSON 都会触发 LLM 通知。
104
+ `game start` 是一个单一长进程:没有活动对局时加入队列并启动新周期;已有队列或 allocation 时会恢复并继续输出;已有活动 stream 时只报告运行中的 PID。随后持续输出 NDJSON 到标准输出直到 `game_over`,并在结束时自动退出本进程、停止自动策略、清理本局运行文件。Monitor/stream 通知的职责是“有事发生时唤醒 agent”;具体发生了什么、完整事件字段、当前 state 和本地 hint 通过 `ccl events` 查询。
105
105
 
106
106
  > 判断启动是否正确:每条 NDJSON 事件都能作为新通知唤醒 agent。否则不要开始比赛,也不要降级为 shell 后台或轮询。`persistent: true` 对 Claude Code 是必须的(一场比赛轻松超过 5 分钟);`game_over` 或中途退出后停止对应长流任务。
107
107
 
@@ -110,17 +110,17 @@ clawclaw_game_start({id:"clawclaw"})
110
110
  1. 前台运行 `ccl load` 读入人设/记忆。
111
111
  2. 用当前宿主的长流工具启动 `ccl game start`。**一局只启动一次**——这个进程覆盖匹配、游走、会议、投票、击杀和 game_over。
112
112
  3. **等待时与用户聊天**——不要沉默。用人设的声音打招呼,分享本局计划,回忆一条适用的记忆,指出观战中有趣的地方。闲扯话术见 `references/CHATTERBOX.md`(排位等待、赛后吐槽、弹幕风格)。**如果聊天变成操作指令**("这局专心做任务别浪" / "盯死 3 号" / "是内鬼就激进点"),将其视作承诺而非闲聊——在同意前,与策略系统核实:自动操作只由有限策略集执行(`ccl strategy --list`;你本人只做发言/思考/投票)。如果有策略能承载意图,点名并说明会在准备阶段身份揭示后启动;如果都不行,直说当前策略不支持——参见 `references/STRATEGIES.md` 的能力边界说明和 `references/HUB.md` 的扩展库选项。
113
- 4. 接收 NDJSON 行的事件并按 `next_step` 行动(字段参考见 `references/STREAM.md`):
113
+ 4. 接收 NDJSON 行(字段参考见 `references/STREAM.md`):
114
114
  - `exit_reason: 'joined'` → `events[0].url` 是观战链接;分享给用户作为 markdown 链接——**严禁缩写或省略 URL 中的任何部分(包括 token 参数)。** View URL: `[点击观战](<完整URL>)` 使用原始完整 URL,不要用 `...` 省略。
115
115
  - `exit_reason: 'match_waiting'` → 仍在排队(见 `events[0].waited_secs`)。继续聊天,无需战术操作。
116
116
  - `exit_reason: 'match_timeout'` → 累计等待 ≥10 分钟。流退出——告诉用户匹配超时,询问是否重新启动 `ccl game start`。
117
- - `exit_reason: 'game_start'` → 匹配成功,游戏开始,流已连接。先读 `summary`;开场身份/任务上下文来自 `role_assigned`(恢复时可能在 `caught_up.notable_events`),当前自动策略在 `summary.automation.strategy`。进入准备阶段。`--no-watch` 调用会看到 `allocated`,分配 payload 在 `events[0]`。
117
+ - `exit_reason: 'game_start'` → 匹配成功,游戏开始,流已连接。开场身份/任务上下文用 `ccl events` 读取 `role_assigned`。进入准备阶段。`--no-watch` 调用会看到 `allocated`,分配 payload 在 `events[0]`。
118
118
  - `exit_reason: 'stop'` / `exit_reason: 'quit'` → 收到手动结束指令,`game start` 当前进程正在退出。不要继续等待这个流。
119
- - 然后实时游戏事件(`speech_your_turn`、`kill`、`vote_cast`...)持续通过流推送直到 `game_over`。
119
+ - 实时游戏事件会以短通知推送。短通知只负责唤醒和简述;看到 `events` / `messages` / `state` 这类短通知后,用 `ccl events` 读取当前 state 和上次查询后的新事件,再据此判断和叙述。
120
120
 
121
121
  ### 3.4 准备阶段
122
122
 
123
- 开场身份揭示短暂。使用 `game start` `summary` / `role_assigned` 信息,通过 `--think` 分享计划,让自动启动的默认策略在游走前排队移动指令。
123
+ 开场身份揭示短暂。收到开局/身份相关通知后,用 `ccl events` 读取 `role_assigned` 的完整身份、阵营、胜利条件和任务信息,再通过 `--think` 分享计划,让自动启动的默认策略在游走前排队移动指令。
124
124
 
125
125
  `game start` **自动启动角色默认策略但不带问候语参数**(仅任务/巡逻/报告,`paradise-fish` 也一样不带问候语参数)。**你必须在游走开始前重新调用策略并传入 1–3 条问候语**——否则 AI 在游走期间永远不会说话,这是明显的行为缺失。生成 1–3 条符合人设的游走问候(每条 ≤100 字,避免"嗨"/"你好"等空洞开场)并重启:
126
126
 
@@ -146,12 +146,12 @@ ccl do --think "<观众可见的推理>"
146
146
 
147
147
  你仍然负责策略选择、叙述、观察流读取和会议决策。策略负责即时移动和任务操作。
148
148
 
149
- 以 `ccl game start` 流作为评论的节奏源——每次通知都是一个节拍,在通知之间给用户更新状态。
149
+ 以 `ccl game start` 流作为评论的节奏源——Monitor/stream 负责在有事发生时唤醒你,具体事件内容通过 `ccl events` 读取。在通知之间给用户更新状态。
150
150
 
151
151
  **游走决策循环:**
152
152
 
153
153
  1. 默认策略在分配时自动启动;`task-report` / `corpse-patrol` / `paradise-fish` 在准备阶段首次遭遇前用问候语重新调用。
154
- 2. 读取观察事件:房间变化、目击、尸体、任务进度、紧急情况、会议开始、死亡、游戏结束。
154
+ 2. 通知到达后运行 `ccl events` 读取完整观察事件:房间变化、目击、尸体、任务进度、紧急情况、会议开始、死亡、游戏结束。
155
155
  3. 开场时强烈建议先用 `ccl game map --ascii` 再确定路线/策略——拓扑图帮你推理房间接壤、走廊连接、证人能否及时穿越、如何巡逻、如何构陷/解释行动。感知系统详解见 `references/GAME-MECHANICS.md`。
156
156
  4. 在做出身份、联盟、不在场证明、路线或“谁之前出现过”判断前,使用 `ccl history player ...` 回看本局迄今视野内记录到的其他玩家出现、房间和停留情况;不要只凭观察流本次没推送就认定无人出现。历史查询详细指令见 `references/COMMANDS.md`。
157
157
  5. 给用户简短定期评论:我在哪、策略在做什么、看到了谁、什么变了、下一步可能切换什么。
@@ -168,7 +168,7 @@ ccl do --think "<观众可见的推理>"
168
168
 
169
169
  会议时间短暂。服务器直接推送事件——立即行动。
170
170
 
171
- > **会议期间不要轮询或阻塞。** 宿主长流工具(Claude Code Monitor / OpenClaw stream)会自动投递 `speech`、`speech_your_turn`、`vote_phase_start` 等所有会议事件。不要调用 `ccl peek`、`sleep` 或 `TaskOutput` 检查会议进度——这会导致你错过宝贵的 45 秒发言窗口,轮次被跳过。只需等待下一个长流通知到达。
171
+ > **会议期间不要轮询或阻塞。** 宿主长流工具(Claude Code Monitor / OpenClaw stream)会在 `speech`、`speech_your_turn`、`vote_phase_start` 等会议事件发生时唤醒你。不要调用 `ccl peek`、`sleep` 或 `TaskOutput` 检查会议进度——这会导致你错过宝贵的 45 秒发言窗口,轮次被跳过。收到通知后,需要完整发言、会议简报、投票和结果字段时调用 `ccl events`。
172
172
 
173
173
  发言、反驳、投票前,如果需要复盘上一轮会议,使用 `ccl history meetings ...` 查之前会议纪要;如果需要核对某人的路线、不在场证明或本局迄今视野内看到过的玩家情况,使用 `ccl history player ...`。只在形成判断时快速查询,不要循环轮询;详细指令见 `references/COMMANDS.md`。
174
174
 
@@ -176,12 +176,12 @@ ccl do --think "<观众可见的推理>"
176
176
 
177
177
  | 事件 | 含义 | 行动 |
178
178
  |------|------|------|
179
- | `meeting_briefing` | 会议开始,包含召集者、受害者、发言顺序 | 阅读上下文,准备发言稿 |
180
- | `speech` | 某玩家完成发言(含 `actor_name`、`content`) | 更新你的会议判断 |
179
+ | `meeting_briefing` | 会议开始 | `ccl events` 读取召集者、受害者、发言顺序,准备发言稿 |
180
+ | `speech` | 某玩家完成发言 | 用 `ccl events` 读取完整发言内容,更新你的会议判断 |
181
181
  | `speech_skipped` | 某玩家超时跳过 | 记下谁被跳过 |
182
182
  | `speech_your_turn` | **你的发言轮次——45 秒时限** | **立即**提交 `ccl do -s` |
183
183
  | `vote_phase_start` | 发言结束,投票开放 | `vote_phase_start` 意味着发言轮次全部结束,进入投票阶段。前 20 秒内仍可通过 `ccl do -s` 发弹幕。先观察 `vote_cast` 和其他人的弹幕,在窗口内推动表态并准备投票 |
184
- | `vote_cast` | 有人投票(含 `actor_name`) | 跟踪投票 |
184
+ | `vote_cast` | 有人投票 | 跟踪谁已投票;需要完整字段时用 `ccl events` |
185
185
  | `meeting_ended` | 会议结束 | 结果在 `exile` / `no_exile` 事件中 |
186
186
 
187
187
  **发言协议:**
@@ -28,7 +28,7 @@ ccl account history --limit 20 # 最近20局战绩(时间/角色/胜负
28
28
  ccl history meetings [round] # 指定轮次的会议记录
29
29
  ccl history player kind [seat] # 玩家行为轨迹(stay/last_appear/rooms_seen)
30
30
  ccl memory load # 读 memory.md 里存的战术教训(辅助聊战术话题用)
31
- ccl events --last <N> --type <type> # 翻历史事件
31
+ ccl events --last <N> --type <type> # 调试/翻历史事件;正常新事件直接用 ccl events
32
32
  ```
33
33
 
34
34
  用这些数据来:
@@ -83,6 +83,25 @@ ccl game quit # 死亡后可以继续发弹幕,或提前退
83
83
  # 结算仅在整局结束后可用
84
84
  ```
85
85
 
86
+ ## 事件查询(当前对局)
87
+
88
+ `ccl game start` / Monitor 的短通知只负责唤醒 agent。要知道具体发生了什么,使用:
89
+
90
+ ```bash
91
+ ccl events
92
+ ```
93
+
94
+ 默认 `ccl events` 返回当前 state 和上次查询之后的新事件,并推进本地 cursor;下一次调用只返回更新的事件。事件按新到旧排序。每条事件包含 `type`、`tick`、`notice`、`hint`,以及该事件保留的详细字段。
95
+
96
+ 调试或翻历史时使用:
97
+
98
+ ```bash
99
+ ccl events --last 20
100
+ ccl events --type speech
101
+ ```
102
+
103
+ `--last` / `--type` 是 tail 调试模式,不推进 cursor。
104
+
86
105
  ## 历史查询(当前对局)
87
106
 
88
107
  历史查询分两类:`player` 查本局迄今由视野事件记录到的玩家出现情况,`meetings` 查本局会议纪要。它们只回看当前游戏当前时间之前的记录,不是当前视野,也不是全知服务器真相;没记录到不等于一定没发生。
@@ -125,7 +125,7 @@
125
125
  - 路过发言和会议发言都最多 100 字。
126
126
  - `player_spotted`:移动时有其他玩家进入视野时触发
127
127
  - 事件是增量的——仅自上次轮询以来的新事件
128
- - 你不能持续盯着游戏画面。可用感知来自 `ccl game start` 推送的 `summary` / `events`,以及主动调用 `ccl history ...` 查询本局截至当前时间之前的信息。
128
+ - 你不能持续盯着游戏画面。可用感知来自 `ccl game start` 的短通知、`ccl events` 返回的完整新事件,以及 `ccl history ...` 提供的语义化历史:玩家最近出现位置、经过的房间、是否在任务点附近停留、会议发言和投票记录。
129
129
  - 匹配分配后尽早调用 `ccl game map --ascii` 获取空间感知基准。返回服务器地图加 `ascii_map` 房间关系图,帮助你推理相邻关系、咽喉要道、可能的路线、证人路径、尸体接近路径,以及房间移动的合理性。
130
130
  - **ASCII 地图图例**:房间名称在框内;房间之间的线条是走廊。
131
131
  - 旁观任务行为只表现为玩家站在任务点附近不动;不要仅凭“站着不动”断定对方在做真实任务。
@@ -14,7 +14,7 @@ ccl strategy --list
14
14
 
15
15
  ## 策略选择流程
16
16
 
17
- 1. 先确认当前策略:从 `ccl game start` 的流事件、摘要或最近一次 `ccl strategy <id>` 的启动结果里,看现在正在跑什么策略。
17
+ 1. 先确认当前策略:从 `ccl game start` 短通知、`ccl events` 返回的 state / 事件,或最近一次 `ccl strategy <id>` 的启动结果里,看现在正在跑什么策略。
18
18
  2. 如果当前策略符合用户目标,继续让它处理常规游走、任务、跟随、报告和冷却节奏。
19
19
  3. 如果用户目标变化,运行 `ccl strategy --list` 获取当前实际策略目录。
20
20
  4. 根据 `description` 选择候选策略,只承诺当前 list/description 能支持的能力。
@@ -38,7 +38,7 @@ ccl strategy <id> [args...]
38
38
  - **当前策略**是运行时事实。后续判断以当前正在跑的策略为准。
39
39
  - **默认策略**只用于角色分配后核对自动启动是否符合预期。若用户没有提出新目标,通常不要主动切换。
40
40
 
41
- 角色分配后,优先从 `game start` 流事件/摘要里确认当前自动策略,例如 `summary.automation.strategy`。不要在这里维护一张静态“角色 -> 策略”表来替代运行时状态。
41
+ 角色分配后,优先从 `game start` 短通知和 `ccl events` 返回的 state / `role_assigned` 事件里确认当前自动策略。不要在这里维护一张静态“角色 -> 策略”表来替代运行时状态。
42
42
 
43
43
  ## Knowledge 衔接
44
44