@myclaw163/clawclaw-cli 0.6.65 → 0.6.66

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.
Files changed (206) hide show
  1. package/README.md +427 -427
  2. package/package.json +48 -48
  3. package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -23
  4. package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -22
  5. package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -22
  6. package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -22
  7. package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -23
  8. package/scripts/find-hide-spots.py +157 -157
  9. package/scripts/postinstall.mjs +20 -20
  10. package/scripts/sync-bundled-skill.mjs +244 -244
  11. package/scripts/sync-bundled-skill.test.mjs +152 -152
  12. package/skills/clawclaw/SKILL.md +245 -244
  13. package/skills/clawclaw/references/CHATTERBOX.md +142 -142
  14. package/skills/clawclaw/references/COMMANDS.md +148 -148
  15. package/skills/clawclaw/references/GAME-MECHANICS.md +188 -186
  16. package/skills/clawclaw/references/HUB.md +48 -48
  17. package/skills/clawclaw/references/KNOWLEDGE.md +43 -43
  18. package/skills/clawclaw/references/STRATEGIES.md +57 -57
  19. package/skills/clawclaw/references/STREAM.md +92 -92
  20. package/skills/clawclaw/references/TACTICS.md +65 -65
  21. package/src/assets/clawclaw-ascii-map.txt +40 -40
  22. package/src/cli.ts +110 -110
  23. package/src/commands/_schema.ts +109 -109
  24. package/src/commands/account.ts +209 -209
  25. package/src/commands/config.ts +30 -30
  26. package/src/commands/do.test.ts +73 -73
  27. package/src/commands/do.ts +126 -126
  28. package/src/commands/events.test.ts +71 -71
  29. package/src/commands/events.ts +155 -155
  30. package/src/commands/game-map.test.ts +28 -28
  31. package/src/commands/game-start-plan.test.ts +84 -84
  32. package/src/commands/game.ts +1027 -1027
  33. package/src/commands/history-player.test.ts +102 -102
  34. package/src/commands/history.ts +573 -573
  35. package/src/commands/hub.test.ts +96 -96
  36. package/src/commands/hub.ts +234 -234
  37. package/src/commands/knowledge.test.ts +19 -19
  38. package/src/commands/knowledge.ts +168 -168
  39. package/src/commands/load.test.ts +51 -51
  40. package/src/commands/load.ts +13 -13
  41. package/src/commands/meeting-history.test.ts +106 -106
  42. package/src/commands/memory.ts +40 -40
  43. package/src/commands/peek.ts +45 -45
  44. package/src/commands/persona.ts +57 -57
  45. package/src/commands/setup/codex.ts +248 -248
  46. package/src/commands/setup/hermes.test.ts +96 -96
  47. package/src/commands/setup/hermes.ts +76 -76
  48. package/src/commands/setup/index.ts +13 -13
  49. package/src/commands/setup/openclaw.test.ts +114 -114
  50. package/src/commands/setup/openclaw.ts +147 -147
  51. package/src/commands/skill.ts +128 -128
  52. package/src/commands/state.ts +46 -46
  53. package/src/commands/strategy.test.ts +135 -135
  54. package/src/commands/strategy.ts +180 -180
  55. package/src/commands/tts.ts +128 -128
  56. package/src/commands/upgrade.test.ts +82 -82
  57. package/src/commands/upgrade.ts +148 -148
  58. package/src/commands/watch.test.ts +966 -966
  59. package/src/commands/watch.ts +659 -659
  60. package/src/lib/auth.test.ts +59 -59
  61. package/src/lib/auth.ts +186 -186
  62. package/src/lib/command-meta.ts +37 -37
  63. package/src/lib/game-client.ts +391 -391
  64. package/src/lib/host-config-patcher.test.ts +130 -130
  65. package/src/lib/host-config-patcher.ts +151 -151
  66. package/src/lib/http-keepalive.ts +15 -15
  67. package/src/lib/http-transport.test.ts +42 -42
  68. package/src/lib/http-transport.ts +113 -113
  69. package/src/lib/hub-client.test.ts +56 -56
  70. package/src/lib/hub-client.ts +88 -88
  71. package/src/lib/hub-install.test.ts +98 -98
  72. package/src/lib/hub-install.ts +121 -121
  73. package/src/lib/hub-reminder.ts +56 -56
  74. package/src/lib/hub-unzip.test.ts +69 -69
  75. package/src/lib/hub-unzip.ts +62 -62
  76. package/src/lib/init-command.test.ts +75 -75
  77. package/src/lib/init-command.ts +120 -120
  78. package/src/lib/knowledge-store.test.ts +180 -180
  79. package/src/lib/knowledge-store.ts +374 -374
  80. package/src/lib/load-context.test.ts +52 -52
  81. package/src/lib/load-context.ts +52 -52
  82. package/src/lib/match-state.test.ts +134 -134
  83. package/src/lib/match-state.ts +94 -94
  84. package/src/lib/netease-tts.ts +83 -83
  85. package/src/lib/normalize.ts +42 -42
  86. package/src/lib/persona.test.ts +41 -41
  87. package/src/lib/persona.ts +72 -72
  88. package/src/lib/server-registry.ts +152 -152
  89. package/src/lib/skill-version.test.ts +48 -48
  90. package/src/lib/skill-version.ts +19 -19
  91. package/src/lib/strategy-export.test.ts +232 -232
  92. package/src/lib/strategy-export.ts +242 -242
  93. package/src/lib/tts-keys.ts +7 -7
  94. package/src/lib/tts-speech.test.ts +63 -63
  95. package/src/lib/tts-speech.ts +76 -76
  96. package/src/lib/workspace-argv.test.ts +49 -49
  97. package/src/lib/workspace-argv.ts +44 -44
  98. package/src/perception/player-history-store.test.ts +87 -87
  99. package/src/perception/player-history-store.ts +194 -194
  100. package/src/pipeline/event-format.test.ts +135 -135
  101. package/src/pipeline/event-format.ts +376 -376
  102. package/src/pipeline/event-hints.ts +173 -173
  103. package/src/pipeline/event-store.test.ts +28 -28
  104. package/src/pipeline/event-store.ts +193 -193
  105. package/src/pipeline/pipeline.ts +35 -35
  106. package/src/runtime/auto-upgrade.test.ts +66 -66
  107. package/src/runtime/auto-upgrade.ts +31 -31
  108. package/src/runtime/event-daemon.test.ts +107 -107
  109. package/src/runtime/event-daemon.ts +409 -409
  110. package/src/runtime/owner-control.ts +150 -150
  111. package/src/runtime/raw-ws-log.test.ts +33 -33
  112. package/src/runtime/raw-ws-log.ts +32 -32
  113. package/src/runtime/runtime-logger.ts +107 -107
  114. package/src/runtime/ws-client.test.ts +104 -104
  115. package/src/runtime/ws-client.ts +272 -272
  116. package/src/sdk/action.ts +166 -166
  117. package/src/sdk/index.ts +111 -111
  118. package/src/sdk/types.ts +159 -159
  119. package/src/strategies/avoid-lone.ts +11 -11
  120. package/src/strategies/avoid-players.knowledge.md +20 -20
  121. package/src/strategies/avoid-players.ts +15 -15
  122. package/src/strategies/corpse-patrol.ts +22 -22
  123. package/src/strategies/crab-sabotage.ts +21 -21
  124. package/src/strategies/custom-module.test.ts +269 -269
  125. package/src/strategies/find-player.ts +16 -16
  126. package/src/strategies/game-utils.test.ts +190 -190
  127. package/src/strategies/game-utils.ts +782 -782
  128. package/src/strategies/goals/anchor-linger.ts +77 -77
  129. package/src/strategies/goals/avoid-lone-top.ts +168 -168
  130. package/src/strategies/goals/avoid-players-top.test.ts +83 -83
  131. package/src/strategies/goals/avoid-players-top.ts +121 -121
  132. package/src/strategies/goals/conversation-goal.ts +51 -51
  133. package/src/strategies/goals/corpse-patrol-top.ts +91 -91
  134. package/src/strategies/goals/crab-octopus-reflexes.ts +93 -93
  135. package/src/strategies/goals/crab-sabotage-top.ts +197 -197
  136. package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
  137. package/src/strategies/goals/find-player-top.ts +93 -93
  138. package/src/strategies/goals/flee-players-goal.ts +53 -53
  139. package/src/strategies/goals/follow-companion-goal.ts +106 -106
  140. package/src/strategies/goals/goal-manager.ts +41 -41
  141. package/src/strategies/goals/goal-root-strategy.ts +49 -49
  142. package/src/strategies/goals/goal.ts +28 -28
  143. package/src/strategies/goals/hide-top.ts +197 -197
  144. package/src/strategies/goals/keep-away-goal.ts +217 -217
  145. package/src/strategies/goals/kill-frenzy-top.ts +80 -80
  146. package/src/strategies/goals/kill-lone-top.ts +160 -160
  147. package/src/strategies/goals/kill-target-goal.ts +59 -59
  148. package/src/strategies/goals/kill-target-top.ts +109 -109
  149. package/src/strategies/goals/leaf-goal.ts +25 -25
  150. package/src/strategies/goals/linger-corpse-goal.ts +35 -35
  151. package/src/strategies/goals/lone-kill-core.ts +82 -82
  152. package/src/strategies/goals/lone-kill-goal.ts +24 -24
  153. package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
  154. package/src/strategies/goals/lone-kill-task-top.ts +86 -86
  155. package/src/strategies/goals/move-room-goal.ts +60 -60
  156. package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
  157. package/src/strategies/goals/normal-shrimp-top.ts +242 -242
  158. package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
  159. package/src/strategies/goals/paradise-fish-top.ts +207 -207
  160. package/src/strategies/goals/patrol-top.ts +57 -57
  161. package/src/strategies/goals/report-patrol-top.ts +80 -80
  162. package/src/strategies/goals/safe-task-goal.ts +102 -102
  163. package/src/strategies/goals/social-task-top.ts +161 -161
  164. package/src/strategies/goals/task-kill-report-top.ts +163 -163
  165. package/src/strategies/goals/task-only-top.ts +57 -57
  166. package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
  167. package/src/strategies/goals/task-report-top.ts +57 -57
  168. package/src/strategies/goals/wander-task-goal.ts +33 -33
  169. package/src/strategies/goals/warrior-shrimp-top.test.ts +86 -86
  170. package/src/strategies/goals/warrior-shrimp-top.ts +500 -500
  171. package/src/strategies/greeting.ts +53 -53
  172. package/src/strategies/hide-spots.ts +123 -123
  173. package/src/strategies/hide.ts +23 -23
  174. package/src/strategies/kill-frenzy.ts +12 -12
  175. package/src/strategies/kill-lone.knowledge.md +20 -20
  176. package/src/strategies/kill-lone.ts +13 -13
  177. package/src/strategies/kill-target.ts +18 -18
  178. package/src/strategies/loader.test.ts +678 -678
  179. package/src/strategies/loader.ts +172 -172
  180. package/src/strategies/lone-kill-task.ts +21 -21
  181. package/src/strategies/meeting-gate.test.ts +59 -59
  182. package/src/strategies/meeting-gate.ts +23 -23
  183. package/src/strategies/move-room.ts +15 -15
  184. package/src/strategies/new-events-backfill.ts +98 -98
  185. package/src/strategies/paradise-fish.knowledge.md +20 -20
  186. package/src/strategies/paradise-fish.ts +25 -25
  187. package/src/strategies/pathfind/distance-field.ts +150 -150
  188. package/src/strategies/pathfind/escape-planner.test.ts +197 -197
  189. package/src/strategies/pathfind/escape-planner.ts +355 -355
  190. package/src/strategies/pathfind/walkable-grid.ts +117 -117
  191. package/src/strategies/patrol.ts +11 -11
  192. package/src/strategies/player-targets.ts +13 -13
  193. package/src/strategies/report-patrol.ts +11 -11
  194. package/src/strategies/shrimp-memory.knowledge.md +20 -20
  195. package/src/strategies/shrimp-memory.ts +25 -25
  196. package/src/strategies/social-task.test.ts +28 -28
  197. package/src/strategies/social-task.ts +49 -49
  198. package/src/strategies/spawn.ts +82 -82
  199. package/src/strategies/speech-module.ts +123 -123
  200. package/src/strategies/strategy-loop.ts +771 -771
  201. package/src/strategies/task-kill-report.ts +17 -17
  202. package/src/strategies/task-only.ts +11 -11
  203. package/src/strategies/task-report.ts +22 -22
  204. package/src/strategies/types.ts +102 -102
  205. package/src/strategies/warrior-memory.knowledge.md +22 -22
  206. package/src/strategies/warrior-memory.ts +16 -16
@@ -1,53 +1,53 @@
1
- import { Action } from '../sdk/action.js';
2
- import type { GameState } from '../sdk/types.js';
3
- import type { BehaviorDecision } from './types.js';
4
- import { TTS_TEXT_MAX_LENGTH } from '../lib/tts-keys.js';
5
- import { stripRoleIds } from './player-targets.js';
6
-
7
- export const MAX_GREETING_PHRASES = 3;
8
- export const GREETING_COOLDOWN_MS = 120_000;
9
-
10
- /**
11
- * 解析「打招呼话术」启动参数:剥掉 role-id 噪声与空串,校验条数(≤3)与单条长度(≤TTS 上限)。
12
- * 供 task-report / corpse-patrol / shrimp-memory / paradise-fish 共用——它们的启动参数语义都是「打招呼话术」。
13
- *
14
- * @param strategyId 仅用于错误文案。
15
- */
16
- export function parseGreetingArgs(args: string[] | undefined, strategyId: string): string[] {
17
- if (!args || args.length === 0) return [];
18
-
19
- const phrases = args.map(a => stripRoleIds(a)).filter(a => a.length > 0);
20
- if (phrases.length === 0) return [];
21
-
22
- if (phrases.length > MAX_GREETING_PHRASES) {
23
- throw new Error(`${strategyId} strategy accepts 0-${MAX_GREETING_PHRASES} greeting phrases (got ${phrases.length}).`);
24
- }
25
- for (const phrase of phrases) {
26
- if (phrase.length > TTS_TEXT_MAX_LENGTH) {
27
- throw new Error(`Greeting phrase too long (${phrase.length} chars, max ${TTS_TEXT_MAX_LENGTH}).`);
28
- }
29
- }
30
- return phrases;
31
- }
32
-
33
- /**
34
- * 打招呼节流器:视野内有人时随机发一条话术,之后 GREETING_COOLDOWN_MS 内不再发。
35
- * 被多个编排型 Top Goal(TaskReportTop / NormalShrimpTop 等)持有复用。
36
- */
37
- export class GreetingTracker {
38
- private lastGreetingAt: number | null = null;
39
-
40
- constructor(private readonly phrases: string[]) {}
41
-
42
- tryGreeting(state: GameState): BehaviorDecision | null {
43
- if (this.phrases.length === 0) return null;
44
- if (state.players.length === 0) return null;
45
-
46
- const now = Date.now();
47
- if (this.lastGreetingAt !== null && now - this.lastGreetingAt < GREETING_COOLDOWN_MS) return null;
48
-
49
- const text = this.phrases[Math.floor(Math.random() * this.phrases.length)];
50
- this.lastGreetingAt = now;
51
- return { action: Action.speech(text) };
52
- }
53
- }
1
+ import { Action } from '../sdk/action.js';
2
+ import type { GameState } from '../sdk/types.js';
3
+ import type { BehaviorDecision } from './types.js';
4
+ import { TTS_TEXT_MAX_LENGTH } from '../lib/tts-keys.js';
5
+ import { stripRoleIds } from './player-targets.js';
6
+
7
+ export const MAX_GREETING_PHRASES = 3;
8
+ export const GREETING_COOLDOWN_MS = 120_000;
9
+
10
+ /**
11
+ * 解析「打招呼话术」启动参数:剥掉 role-id 噪声与空串,校验条数(≤3)与单条长度(≤TTS 上限)。
12
+ * 供 task-report / corpse-patrol / shrimp-memory / paradise-fish 共用——它们的启动参数语义都是「打招呼话术」。
13
+ *
14
+ * @param strategyId 仅用于错误文案。
15
+ */
16
+ export function parseGreetingArgs(args: string[] | undefined, strategyId: string): string[] {
17
+ if (!args || args.length === 0) return [];
18
+
19
+ const phrases = args.map(a => stripRoleIds(a)).filter(a => a.length > 0);
20
+ if (phrases.length === 0) return [];
21
+
22
+ if (phrases.length > MAX_GREETING_PHRASES) {
23
+ throw new Error(`${strategyId} strategy accepts 0-${MAX_GREETING_PHRASES} greeting phrases (got ${phrases.length}).`);
24
+ }
25
+ for (const phrase of phrases) {
26
+ if (phrase.length > TTS_TEXT_MAX_LENGTH) {
27
+ throw new Error(`Greeting phrase too long (${phrase.length} chars, max ${TTS_TEXT_MAX_LENGTH}).`);
28
+ }
29
+ }
30
+ return phrases;
31
+ }
32
+
33
+ /**
34
+ * 打招呼节流器:视野内有人时随机发一条话术,之后 GREETING_COOLDOWN_MS 内不再发。
35
+ * 被多个编排型 Top Goal(TaskReportTop / NormalShrimpTop 等)持有复用。
36
+ */
37
+ export class GreetingTracker {
38
+ private lastGreetingAt: number | null = null;
39
+
40
+ constructor(private readonly phrases: string[]) {}
41
+
42
+ tryGreeting(state: GameState): BehaviorDecision | null {
43
+ if (this.phrases.length === 0) return null;
44
+ if (state.players.length === 0) return null;
45
+
46
+ const now = Date.now();
47
+ if (this.lastGreetingAt !== null && now - this.lastGreetingAt < GREETING_COOLDOWN_MS) return null;
48
+
49
+ const text = this.phrases[Math.floor(Math.random() * this.phrases.length)];
50
+ this.lastGreetingAt = now;
51
+ return { action: Action.speech(text) };
52
+ }
53
+ }
@@ -1,123 +1,123 @@
1
- import type { Position } from '../sdk/types.js';
2
- import { dist } from './game-utils.js';
3
- import { assessRoutes } from './pathfind/escape-planner.js';
4
-
5
- export interface HideSpot {
6
- /** 世界像素坐标,与 GameState / 可行走网格同坐标系。 */
7
- x: number;
8
- y: number;
9
- /** 所在房间,仅供日志/调试。 */
10
- room: string;
11
- }
12
-
13
- /**
14
- * 躲藏点:离线从服务端烘焙地图(config/clawclaw/clawclaw.tmj.baked.npz)算出的「藏身角落」。筛选条件:
15
- * - 所在房间有 ≥2 个出口(非死胡同,被追能逃);**走廊**(做任务的必经路线)一律排除;
16
- * - 出口 ≥4 的枢纽房间排除(穿行人流太大,藏不住);
17
- * - 取房间内「离各出口最深、且远离任务点/出生点」的可走格(缩在内侧角落,避开穿行线与刷新点)。
18
- *
19
- * 重新生成:scripts/find-hide-spots.py(用后端 venv 解释器跑,读 baked.npz,打印可粘贴的本数组)。
20
- * 这是离线烘焙数据,和 pathfind 的可行走网格一样:运行时按需 snap 到最近可走格即可。
21
- */
22
- export const HIDE_SPOTS: readonly HideSpot[] = [
23
- { x: 2882, y: 1362, room: '导航仓' },
24
- { x: 2386, y: 1138, room: '健身房' },
25
- { x: 1922, y: 1234, room: '酒吧' },
26
- { x: 2418, y: 2130, room: '休闲会所' },
27
- { x: 1538, y: 1826, room: '监控室' },
28
- { x: 514, y: 2402, room: '动力监控室' },
29
- { x: 1538, y: 2514, room: '自助餐厅' },
30
- { x: 1314, y: 2674, room: '制氧舱' },
31
- { x: 2514, y: 2882, room: '中央厨房' },
32
- { x: 2882, y: 2994, room: '冷库' },
33
- { x: 3858, y: 2434, room: '情报室' },
34
- ];
35
-
36
- /** 躲藏点离任一威胁多近就硬排除(与 nearestSafeTask 的端点排除同量级)。 */
37
- export const HIDE_THREAT_EXCLUDE_RANGE = 500;
38
- /** 去躲藏点的测地路径经过威胁这个距离内也排除(避免走向把守者)。 */
39
- export const HIDE_PATH_THREAT_RADIUS = 350;
40
-
41
- export interface NearestSafeHideOptions {
42
- threatExcludeRadius?: number;
43
- pathThreatRadius?: number;
44
- /** 当前已选躲藏点:仍是合法候选就保持不变,杜绝等距摇摆。 */
45
- stickyTo?: HideSpot | null;
46
- /** 被标记为不可达的移动目标:在其附近的点跳过。 */
47
- blockedTarget?: Position | null;
48
- }
49
-
50
- const BLOCKED_RADIUS = 12;
51
-
52
- function sameSpot(a: HideSpot, b: HideSpot): boolean {
53
- return a.x === b.x && a.y === b.y;
54
- }
55
-
56
- /** 所有点都被威胁封死时的兜底:离威胁最远的点(仍好过原地等死)。 */
57
- function farthestFromThreats(threatPoints: Position[], blocked?: Position | null): HideSpot | null {
58
- let best: HideSpot | null = null;
59
- let bestMin = -Infinity;
60
- for (const s of HIDE_SPOTS) {
61
- if (blocked && dist(s.x, s.y, blocked.x, blocked.y) <= BLOCKED_RADIUS) continue;
62
- const minD = threatPoints.length === 0 ? 0 : Math.min(...threatPoints.map(p => dist(s.x, s.y, p.x, p.y)));
63
- if (minD > bestMin) {
64
- bestMin = minD;
65
- best = s;
66
- }
67
- }
68
- return best;
69
- }
70
-
71
- /**
72
- * 选「最近的安全躲藏点」:与 nearestSafeTask 同构——威胁旁的点硬排除、去路经过威胁附近的点排除,
73
- * 余者取测地最近(distance-field 一次扫描;测地不可达按欧氏垫底),带粘性(当前点仍合法就不换)。
74
- * 全部被威胁封死时退回「离威胁最远」的点。
75
- */
76
- export function nearestSafeHideSpot(
77
- from: Position,
78
- threatPoints: Position[] = [],
79
- opts: NearestSafeHideOptions = {},
80
- ): HideSpot | null {
81
- const excludeR = opts.threatExcludeRadius ?? HIDE_THREAT_EXCLUDE_RANGE;
82
- const blocked = opts.blockedTarget ?? null;
83
-
84
- const candidates = HIDE_SPOTS.filter(s =>
85
- (!blocked || dist(s.x, s.y, blocked.x, blocked.y) > BLOCKED_RADIUS)
86
- && !threatPoints.some(p => dist(s.x, s.y, p.x, p.y) <= excludeR));
87
-
88
- if (candidates.length === 0) return farthestFromThreats(threatPoints, blocked);
89
-
90
- // 无威胁时粘性可零成本短路(不必为路径检查扫距离场)。
91
- if (opts.stickyTo && threatPoints.length === 0) {
92
- const sticky = candidates.find(c => sameSpot(c, opts.stickyTo!));
93
- if (sticky) return sticky;
94
- }
95
-
96
- const routes = assessRoutes(
97
- from,
98
- candidates.map(c => ({ x: c.x, y: c.y })),
99
- threatPoints,
100
- opts.pathThreatRadius ?? HIDE_PATH_THREAT_RADIUS,
101
- );
102
- const indexed = candidates.map((c, i) => ({ c, i }));
103
- const viable = routes == null ? indexed : indexed.filter(({ i }) => !routes[i].nearThreat);
104
- const pool = viable.length > 0 ? viable : indexed;
105
-
106
- if (opts.stickyTo) {
107
- const sticky = pool.find(({ c }) => sameSpot(c, opts.stickyTo!));
108
- if (sticky) return sticky.c;
109
- }
110
-
111
- let best = pool[0].c;
112
- let bestScore = Infinity;
113
- for (const { c, i } of pool) {
114
- const euclid = dist(from.x, from.y, c.x, c.y);
115
- const geo = routes?.[i].distancePx;
116
- const score = geo == null ? euclid : geo !== Infinity ? geo : euclid + 1e6;
117
- if (score < bestScore) {
118
- bestScore = score;
119
- best = c;
120
- }
121
- }
122
- return best;
123
- }
1
+ import type { Position } from '../sdk/types.js';
2
+ import { dist } from './game-utils.js';
3
+ import { assessRoutes } from './pathfind/escape-planner.js';
4
+
5
+ export interface HideSpot {
6
+ /** 世界像素坐标,与 GameState / 可行走网格同坐标系。 */
7
+ x: number;
8
+ y: number;
9
+ /** 所在房间,仅供日志/调试。 */
10
+ room: string;
11
+ }
12
+
13
+ /**
14
+ * 躲藏点:离线从服务端烘焙地图(config/clawclaw/clawclaw.tmj.baked.npz)算出的「藏身角落」。筛选条件:
15
+ * - 所在房间有 ≥2 个出口(非死胡同,被追能逃);**走廊**(做任务的必经路线)一律排除;
16
+ * - 出口 ≥4 的枢纽房间排除(穿行人流太大,藏不住);
17
+ * - 取房间内「离各出口最深、且远离任务点/出生点」的可走格(缩在内侧角落,避开穿行线与刷新点)。
18
+ *
19
+ * 重新生成:scripts/find-hide-spots.py(用后端 venv 解释器跑,读 baked.npz,打印可粘贴的本数组)。
20
+ * 这是离线烘焙数据,和 pathfind 的可行走网格一样:运行时按需 snap 到最近可走格即可。
21
+ */
22
+ export const HIDE_SPOTS: readonly HideSpot[] = [
23
+ { x: 2882, y: 1362, room: '导航仓' },
24
+ { x: 2386, y: 1138, room: '健身房' },
25
+ { x: 1922, y: 1234, room: '酒吧' },
26
+ { x: 2418, y: 2130, room: '休闲会所' },
27
+ { x: 1538, y: 1826, room: '监控室' },
28
+ { x: 514, y: 2402, room: '动力监控室' },
29
+ { x: 1538, y: 2514, room: '自助餐厅' },
30
+ { x: 1314, y: 2674, room: '制氧舱' },
31
+ { x: 2514, y: 2882, room: '中央厨房' },
32
+ { x: 2882, y: 2994, room: '冷库' },
33
+ { x: 3858, y: 2434, room: '情报室' },
34
+ ];
35
+
36
+ /** 躲藏点离任一威胁多近就硬排除(与 nearestSafeTask 的端点排除同量级)。 */
37
+ export const HIDE_THREAT_EXCLUDE_RANGE = 500;
38
+ /** 去躲藏点的测地路径经过威胁这个距离内也排除(避免走向把守者)。 */
39
+ export const HIDE_PATH_THREAT_RADIUS = 350;
40
+
41
+ export interface NearestSafeHideOptions {
42
+ threatExcludeRadius?: number;
43
+ pathThreatRadius?: number;
44
+ /** 当前已选躲藏点:仍是合法候选就保持不变,杜绝等距摇摆。 */
45
+ stickyTo?: HideSpot | null;
46
+ /** 被标记为不可达的移动目标:在其附近的点跳过。 */
47
+ blockedTarget?: Position | null;
48
+ }
49
+
50
+ const BLOCKED_RADIUS = 12;
51
+
52
+ function sameSpot(a: HideSpot, b: HideSpot): boolean {
53
+ return a.x === b.x && a.y === b.y;
54
+ }
55
+
56
+ /** 所有点都被威胁封死时的兜底:离威胁最远的点(仍好过原地等死)。 */
57
+ function farthestFromThreats(threatPoints: Position[], blocked?: Position | null): HideSpot | null {
58
+ let best: HideSpot | null = null;
59
+ let bestMin = -Infinity;
60
+ for (const s of HIDE_SPOTS) {
61
+ if (blocked && dist(s.x, s.y, blocked.x, blocked.y) <= BLOCKED_RADIUS) continue;
62
+ const minD = threatPoints.length === 0 ? 0 : Math.min(...threatPoints.map(p => dist(s.x, s.y, p.x, p.y)));
63
+ if (minD > bestMin) {
64
+ bestMin = minD;
65
+ best = s;
66
+ }
67
+ }
68
+ return best;
69
+ }
70
+
71
+ /**
72
+ * 选「最近的安全躲藏点」:与 nearestSafeTask 同构——威胁旁的点硬排除、去路经过威胁附近的点排除,
73
+ * 余者取测地最近(distance-field 一次扫描;测地不可达按欧氏垫底),带粘性(当前点仍合法就不换)。
74
+ * 全部被威胁封死时退回「离威胁最远」的点。
75
+ */
76
+ export function nearestSafeHideSpot(
77
+ from: Position,
78
+ threatPoints: Position[] = [],
79
+ opts: NearestSafeHideOptions = {},
80
+ ): HideSpot | null {
81
+ const excludeR = opts.threatExcludeRadius ?? HIDE_THREAT_EXCLUDE_RANGE;
82
+ const blocked = opts.blockedTarget ?? null;
83
+
84
+ const candidates = HIDE_SPOTS.filter(s =>
85
+ (!blocked || dist(s.x, s.y, blocked.x, blocked.y) > BLOCKED_RADIUS)
86
+ && !threatPoints.some(p => dist(s.x, s.y, p.x, p.y) <= excludeR));
87
+
88
+ if (candidates.length === 0) return farthestFromThreats(threatPoints, blocked);
89
+
90
+ // 无威胁时粘性可零成本短路(不必为路径检查扫距离场)。
91
+ if (opts.stickyTo && threatPoints.length === 0) {
92
+ const sticky = candidates.find(c => sameSpot(c, opts.stickyTo!));
93
+ if (sticky) return sticky;
94
+ }
95
+
96
+ const routes = assessRoutes(
97
+ from,
98
+ candidates.map(c => ({ x: c.x, y: c.y })),
99
+ threatPoints,
100
+ opts.pathThreatRadius ?? HIDE_PATH_THREAT_RADIUS,
101
+ );
102
+ const indexed = candidates.map((c, i) => ({ c, i }));
103
+ const viable = routes == null ? indexed : indexed.filter(({ i }) => !routes[i].nearThreat);
104
+ const pool = viable.length > 0 ? viable : indexed;
105
+
106
+ if (opts.stickyTo) {
107
+ const sticky = pool.find(({ c }) => sameSpot(c, opts.stickyTo!));
108
+ if (sticky) return sticky.c;
109
+ }
110
+
111
+ let best = pool[0].c;
112
+ let bestScore = Infinity;
113
+ for (const { c, i } of pool) {
114
+ const euclid = dist(from.x, from.y, c.x, c.y);
115
+ const geo = routes?.[i].distancePx;
116
+ const score = geo == null ? euclid : geo !== Infinity ? geo : euclid + 1e6;
117
+ if (score < bestScore) {
118
+ bestScore = score;
119
+ best = c;
120
+ }
121
+ }
122
+ return best;
123
+ }
@@ -1,23 +1,23 @@
1
- import type { StrategyEntry } from './types.js';
2
- import { GoalRootStrategy } from './goals/goal-root-strategy.js';
3
- import { HideTop } from './goals/hide-top.js';
4
-
5
- /** 解析参数:kill/fight → 死角自保开;nokill/flee/pacifist → 关;缺省(null)= 按角色默认。 */
6
- export function parseHideKillArg(args?: string[]): boolean | null {
7
- for (const raw of args ?? []) {
8
- const a = raw.trim().toLowerCase();
9
- if (a === 'kill' || a === 'fight' || a === 'kill=on') return true;
10
- if (a === 'nokill' || a === 'flee' || a === 'pacifist' || a === 'kill=off') return false;
11
- }
12
- return null;
13
- }
14
-
15
- export const strategy: StrategyEntry = {
16
- id: 'hide',
17
- description:
18
- '躲藏策略:在离线从地图算好的若干「非走廊、≥2 出口可逃、远离任务点/出生点」的藏身角落里,选测地最近且不挨威胁的一个潜伏不动;视野里一出现非队友,就像 shrimp-memory 那样保持距离甩开(KeepAway),并边逃边重算排除该威胁的新藏点,逃完落到新点继续潜伏。蟹不躲队友。可选参数 kill / nokill 控制「被单个对手逼进出刀距离且无路可逃时是否出刀自保」——缺省按角色:蟹/章鱼默认开,武士虾/枪虾(及无刀角色)默认关。',
19
- create(args?: string[]) {
20
- const killWhenCornered = parseHideKillArg(args);
21
- return new GoalRootStrategy('hide', () => new HideTop(killWhenCornered), { resetOnMeetingResume: false });
22
- },
23
- };
1
+ import type { StrategyEntry } from './types.js';
2
+ import { GoalRootStrategy } from './goals/goal-root-strategy.js';
3
+ import { HideTop } from './goals/hide-top.js';
4
+
5
+ /** 解析参数:kill/fight → 死角自保开;nokill/flee/pacifist → 关;缺省(null)= 按角色默认。 */
6
+ export function parseHideKillArg(args?: string[]): boolean | null {
7
+ for (const raw of args ?? []) {
8
+ const a = raw.trim().toLowerCase();
9
+ if (a === 'kill' || a === 'fight' || a === 'kill=on') return true;
10
+ if (a === 'nokill' || a === 'flee' || a === 'pacifist' || a === 'kill=off') return false;
11
+ }
12
+ return null;
13
+ }
14
+
15
+ export const strategy: StrategyEntry = {
16
+ id: 'hide',
17
+ description:
18
+ '躲藏策略:在离线从地图算好的若干「非走廊、≥2 出口可逃、远离任务点/出生点」的藏身角落里,选测地最近且不挨威胁的一个潜伏不动;视野里一出现非队友,就像 shrimp-memory 那样保持距离甩开(KeepAway),并边逃边重算排除该威胁的新藏点,逃完落到新点继续潜伏。蟹不躲队友。可选参数 kill / nokill 控制「被单个对手逼进出刀距离且无路可逃时是否出刀自保」——缺省按角色:蟹/章鱼默认开,武士虾/枪虾(及无刀角色)默认关。',
19
+ create(args?: string[]) {
20
+ const killWhenCornered = parseHideKillArg(args);
21
+ return new GoalRootStrategy('hide', () => new HideTop(killWhenCornered), { resetOnMeetingResume: false });
22
+ },
23
+ };
@@ -1,12 +1,12 @@
1
- import type { StrategyEntry } from './types.js';
2
- import { GoalRootStrategy } from './goals/goal-root-strategy.js';
3
- import { KillFrenzyTop } from './goals/kill-frenzy-top.js';
4
-
5
- export const strategy: StrategyEntry = {
6
- id: 'kill-frenzy',
7
- description:
8
- '疯狂砍人:见谁杀谁(除队友外所有可见玩家),多人在场也照杀。按房间顺序巡逻,视野里只要有非队友就冲向最近的那个,50内且冷却好就出刀,冷却中保持10距离贴身跟随等冷却。与 kill-lone 不同,不会因为周围人多而暂停攻击。永不停手。',
9
- create() {
10
- return new GoalRootStrategy('kill-frenzy', () => new KillFrenzyTop(), { resetOnMeetingResume: false });
11
- },
12
- };
1
+ import type { StrategyEntry } from './types.js';
2
+ import { GoalRootStrategy } from './goals/goal-root-strategy.js';
3
+ import { KillFrenzyTop } from './goals/kill-frenzy-top.js';
4
+
5
+ export const strategy: StrategyEntry = {
6
+ id: 'kill-frenzy',
7
+ description:
8
+ '疯狂砍人:见谁杀谁(除队友外所有可见玩家),多人在场也照杀。按房间顺序巡逻,视野里只要有非队友就冲向最近的那个,50内且冷却好就出刀,冷却中保持10距离贴身跟随等冷却。与 kill-lone 不同,不会因为周围人多而暂停攻击。永不停手。',
9
+ create() {
10
+ return new GoalRootStrategy('kill-frenzy', () => new KillFrenzyTop(), { resetOnMeetingResume: false });
11
+ },
12
+ };
@@ -1,20 +1,20 @@
1
- # kill-lone 知识契约
2
-
3
- `kill-lone` 读取统一的玩家三档标记:
4
-
5
- | 标记 | 行为 |
6
- |------|------|
7
- | `suspect` | 不升级为主动猎杀目标,仍按默认落单规则判断 |
8
- | `hostile` | 视为明确猎杀目标,见到就追杀,可突破多人保护规则 |
9
- | `trusted` | 永不击杀,优先级高于其他猎杀判断 |
10
-
11
- 未标记玩家维持默认行为:仅猎杀落单的非队友。
12
-
13
- ```bash
14
- ccl knowledge mark 5 suspect --confidence 0.7 --note "行为可疑"
15
- ccl knowledge mark 5 hostile --confidence 0.9 --note "确认敌对"
16
- ccl knowledge mark 3 trusted --note "已澄清"
17
- ccl knowledge del player 5
18
- ```
19
-
20
- `role` 是纯身份事实,不直接控制策略。
1
+ # kill-lone 知识契约
2
+
3
+ `kill-lone` 读取统一的玩家三档标记:
4
+
5
+ | 标记 | 行为 |
6
+ |------|------|
7
+ | `suspect` | 不升级为主动猎杀目标,仍按默认落单规则判断 |
8
+ | `hostile` | 视为明确猎杀目标,见到就追杀,可突破多人保护规则 |
9
+ | `trusted` | 永不击杀,优先级高于其他猎杀判断 |
10
+
11
+ 未标记玩家维持默认行为:仅猎杀落单的非队友。
12
+
13
+ ```bash
14
+ ccl knowledge mark 5 suspect --confidence 0.7 --note "行为可疑"
15
+ ccl knowledge mark 5 hostile --confidence 0.9 --note "确认敌对"
16
+ ccl knowledge mark 3 trusted --note "已澄清"
17
+ ccl knowledge del player 5
18
+ ```
19
+
20
+ `role` 是纯身份事实,不直接控制策略。
@@ -1,13 +1,13 @@
1
- import type { StrategyEntry } from './types.js';
2
- import { GoalRootStrategy } from './goals/goal-root-strategy.js';
3
- import { KillLoneTop } from './goals/kill-lone-top.js';
4
- import { parseTargetArgs } from './player-targets.js';
5
-
6
- export const strategy: StrategyEntry = {
7
- id: 'kill-lone',
8
- description: '按照房间顺序进行巡逻,巡逻时遇到人会判断。可传入避免击杀的座位号或名字列表。视野里只有一个非队友且不在避免名单内就靠近,进入50距离就出刀;冷却中贴在10距离附近等。视野里出现两人以上时,暂停攻击10秒并继续巡逻。',
9
- create(args?: string[]) {
10
- const protectedTargets = args ? parseTargetArgs(args) : [];
11
- return new GoalRootStrategy('kill-lone', () => new KillLoneTop(protectedTargets), { resetOnMeetingResume: false });
12
- },
13
- };
1
+ import type { StrategyEntry } from './types.js';
2
+ import { GoalRootStrategy } from './goals/goal-root-strategy.js';
3
+ import { KillLoneTop } from './goals/kill-lone-top.js';
4
+ import { parseTargetArgs } from './player-targets.js';
5
+
6
+ export const strategy: StrategyEntry = {
7
+ id: 'kill-lone',
8
+ description: '按照房间顺序进行巡逻,巡逻时遇到人会判断。可传入避免击杀的座位号或名字列表。视野里只有一个非队友且不在避免名单内就靠近,进入50距离就出刀;冷却中贴在10距离附近等。视野里出现两人以上时,暂停攻击10秒并继续巡逻。',
9
+ create(args?: string[]) {
10
+ const protectedTargets = args ? parseTargetArgs(args) : [];
11
+ return new GoalRootStrategy('kill-lone', () => new KillLoneTop(protectedTargets), { resetOnMeetingResume: false });
12
+ },
13
+ };
@@ -1,18 +1,18 @@
1
- import type { StrategyEntry } from './types.js';
2
- import { GoalRootStrategy } from './goals/goal-root-strategy.js';
3
- import { KillTargetTop } from './goals/kill-target-top.js';
4
- import { parseTargetArgs } from './player-targets.js';
5
-
6
- export const strategy: StrategyEntry = {
7
- id: 'kill-target',
8
- description: '专门追杀指定目标(传入座位号或名字,可多人)。按照房间顺序进行巡逻,目标进入视野就冲上去,50内且冷却好就出刀;冷却中保持10距离贴身跟随等待。找目标时路过其他玩家不会停步。所有目标死亡后停止。',
9
- create(args?: string[]) {
10
- const targets = args ? parseTargetArgs(args) : [];
11
- if (targets.length === 0) {
12
- throw new Error(
13
- 'kill-target strategy requires target seat numbers or player names (comma- or space-separated).',
14
- );
15
- }
16
- return new GoalRootStrategy('kill-target', () => new KillTargetTop(targets), { resetOnMeetingResume: false });
17
- },
18
- };
1
+ import type { StrategyEntry } from './types.js';
2
+ import { GoalRootStrategy } from './goals/goal-root-strategy.js';
3
+ import { KillTargetTop } from './goals/kill-target-top.js';
4
+ import { parseTargetArgs } from './player-targets.js';
5
+
6
+ export const strategy: StrategyEntry = {
7
+ id: 'kill-target',
8
+ description: '专门追杀指定目标(传入座位号或名字,可多人)。按照房间顺序进行巡逻,目标进入视野就冲上去,50内且冷却好就出刀;冷却中保持10距离贴身跟随等待。找目标时路过其他玩家不会停步。所有目标死亡后停止。',
9
+ create(args?: string[]) {
10
+ const targets = args ? parseTargetArgs(args) : [];
11
+ if (targets.length === 0) {
12
+ throw new Error(
13
+ 'kill-target strategy requires target seat numbers or player names (comma- or space-separated).',
14
+ );
15
+ }
16
+ return new GoalRootStrategy('kill-target', () => new KillTargetTop(targets), { resetOnMeetingResume: false });
17
+ },
18
+ };