@myclaw163/clawclaw-cli 0.6.70 → 0.6.74

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 (212) hide show
  1. package/README.md +377 -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/check-skill-command-surface.mjs +116 -0
  9. package/scripts/find-hide-spots.py +157 -157
  10. package/scripts/postinstall.mjs +20 -20
  11. package/scripts/sync-bundled-skill.mjs +244 -244
  12. package/scripts/sync-bundled-skill.test.mjs +152 -152
  13. package/skills/clawclaw/SKILL.md +246 -244
  14. package/skills/clawclaw/references/CHATTERBOX.md +141 -142
  15. package/skills/clawclaw/references/COMMANDS.md +155 -148
  16. package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
  17. package/skills/clawclaw/references/HUB.md +48 -48
  18. package/skills/clawclaw/references/KNOWLEDGE.md +42 -45
  19. package/skills/clawclaw/references/STRATEGIES.md +59 -59
  20. package/skills/clawclaw/references/STREAM.md +93 -91
  21. package/skills/clawclaw/references/TACTICS.md +65 -65
  22. package/src/assets/clawclaw-ascii-map.txt +40 -40
  23. package/src/cli.ts +110 -110
  24. package/src/commands/_schema.ts +124 -109
  25. package/src/commands/account.ts +209 -209
  26. package/src/commands/config.ts +30 -30
  27. package/src/commands/do.test.ts +84 -73
  28. package/src/commands/do.ts +130 -126
  29. package/src/commands/events.test.ts +71 -71
  30. package/src/commands/events.ts +221 -155
  31. package/src/commands/game-map.test.ts +28 -28
  32. package/src/commands/game-start-plan.test.ts +84 -84
  33. package/src/commands/game.ts +1113 -1042
  34. package/src/commands/history-player.test.ts +102 -102
  35. package/src/commands/history.ts +573 -573
  36. package/src/commands/hub.test.ts +96 -96
  37. package/src/commands/hub.ts +234 -234
  38. package/src/commands/knowledge.test.ts +13 -13
  39. package/src/commands/knowledge.ts +139 -139
  40. package/src/commands/load.test.ts +51 -51
  41. package/src/commands/load.ts +13 -13
  42. package/src/commands/meeting-history.test.ts +106 -106
  43. package/src/commands/memory.ts +40 -40
  44. package/src/commands/peek.ts +45 -45
  45. package/src/commands/persona.ts +57 -57
  46. package/src/commands/setup/codex.ts +266 -266
  47. package/src/commands/setup/hermes.test.ts +96 -96
  48. package/src/commands/setup/hermes.ts +76 -76
  49. package/src/commands/setup/index.ts +13 -13
  50. package/src/commands/setup/openclaw.test.ts +114 -114
  51. package/src/commands/setup/openclaw.ts +147 -147
  52. package/src/commands/skill.ts +128 -128
  53. package/src/commands/state.ts +46 -46
  54. package/src/commands/strategy.test.ts +145 -145
  55. package/src/commands/strategy.ts +181 -181
  56. package/src/commands/tts.ts +128 -128
  57. package/src/commands/upgrade.test.ts +82 -82
  58. package/src/commands/upgrade.ts +148 -148
  59. package/src/commands/watch.test.ts +999 -977
  60. package/src/commands/watch.ts +660 -658
  61. package/src/lib/auth.test.ts +74 -74
  62. package/src/lib/auth.ts +186 -186
  63. package/src/lib/command-meta.ts +37 -37
  64. package/src/lib/game-client.ts +403 -391
  65. package/src/lib/game-context.ts +92 -0
  66. package/src/lib/host-config-patcher.test.ts +130 -130
  67. package/src/lib/host-config-patcher.ts +151 -151
  68. package/src/lib/http-keepalive.ts +15 -15
  69. package/src/lib/http-transport.test.ts +42 -42
  70. package/src/lib/http-transport.ts +113 -113
  71. package/src/lib/hub-client.test.ts +56 -56
  72. package/src/lib/hub-client.ts +88 -88
  73. package/src/lib/hub-install.test.ts +98 -98
  74. package/src/lib/hub-install.ts +121 -121
  75. package/src/lib/hub-reminder.ts +56 -56
  76. package/src/lib/hub-unzip.test.ts +69 -69
  77. package/src/lib/hub-unzip.ts +62 -62
  78. package/src/lib/init-command.test.ts +75 -75
  79. package/src/lib/init-command.ts +120 -120
  80. package/src/lib/knowledge-store.test.ts +170 -170
  81. package/src/lib/knowledge-store.ts +369 -369
  82. package/src/lib/load-context.test.ts +52 -52
  83. package/src/lib/load-context.ts +52 -52
  84. package/src/lib/match-state.test.ts +134 -134
  85. package/src/lib/match-state.ts +94 -94
  86. package/src/lib/netease-tts.ts +83 -83
  87. package/src/lib/normalize.ts +42 -42
  88. package/src/lib/persona.test.ts +41 -41
  89. package/src/lib/persona.ts +72 -72
  90. package/src/lib/server-registry.ts +152 -152
  91. package/src/lib/skill-version.test.ts +48 -48
  92. package/src/lib/skill-version.ts +19 -19
  93. package/src/lib/strategy-export.test.ts +232 -232
  94. package/src/lib/strategy-export.ts +242 -242
  95. package/src/lib/tts-keys.ts +7 -7
  96. package/src/lib/tts-speech.test.ts +63 -63
  97. package/src/lib/tts-speech.ts +76 -76
  98. package/src/lib/workspace-argv.test.ts +49 -49
  99. package/src/lib/workspace-argv.ts +44 -44
  100. package/src/perception/player-history-store.test.ts +87 -87
  101. package/src/perception/player-history-store.ts +194 -194
  102. package/src/pipeline/event-format.test.ts +243 -215
  103. package/src/pipeline/event-format.ts +501 -485
  104. package/src/pipeline/event-hints.ts +195 -190
  105. package/src/pipeline/event-store.test.ts +28 -28
  106. package/src/pipeline/event-store.ts +193 -193
  107. package/src/pipeline/pipeline.ts +35 -35
  108. package/src/pipeline/player-projection.test.ts +119 -0
  109. package/src/pipeline/player-projection.ts +380 -0
  110. package/src/runtime/auto-upgrade.test.ts +66 -66
  111. package/src/runtime/auto-upgrade.ts +31 -31
  112. package/src/runtime/event-daemon.test.ts +209 -141
  113. package/src/runtime/event-daemon.ts +519 -457
  114. package/src/runtime/owner-control.ts +150 -150
  115. package/src/runtime/raw-ws-log.test.ts +33 -33
  116. package/src/runtime/raw-ws-log.ts +32 -32
  117. package/src/runtime/runtime-logger.ts +107 -107
  118. package/src/runtime/ws-client.test.ts +125 -104
  119. package/src/runtime/ws-client.ts +287 -272
  120. package/src/sdk/action.ts +166 -166
  121. package/src/sdk/index.ts +110 -110
  122. package/src/sdk/types.ts +161 -161
  123. package/src/strategies/avoid-lone.ts +12 -12
  124. package/src/strategies/avoid-players.knowledge.md +19 -19
  125. package/src/strategies/avoid-players.ts +16 -16
  126. package/src/strategies/corpse-patrol.ts +23 -23
  127. package/src/strategies/crab-sabotage.ts +22 -22
  128. package/src/strategies/custom-module.test.ts +270 -270
  129. package/src/strategies/find-player.ts +17 -17
  130. package/src/strategies/game-utils.test.ts +242 -242
  131. package/src/strategies/game-utils.ts +846 -846
  132. package/src/strategies/goals/anchor-linger.ts +77 -77
  133. package/src/strategies/goals/avoid-lone-top.ts +168 -168
  134. package/src/strategies/goals/avoid-players-top.test.ts +83 -83
  135. package/src/strategies/goals/avoid-players-top.ts +121 -121
  136. package/src/strategies/goals/conversation-goal.ts +51 -51
  137. package/src/strategies/goals/corpse-patrol-top.ts +113 -113
  138. package/src/strategies/goals/crab-octopus-reflexes.ts +101 -101
  139. package/src/strategies/goals/crab-sabotage-top.ts +197 -197
  140. package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
  141. package/src/strategies/goals/find-player-top.ts +93 -93
  142. package/src/strategies/goals/flee-players-goal.ts +53 -53
  143. package/src/strategies/goals/follow-companion-goal.ts +106 -106
  144. package/src/strategies/goals/goal-manager.ts +41 -41
  145. package/src/strategies/goals/goal-root-strategy.ts +49 -49
  146. package/src/strategies/goals/goal.ts +28 -28
  147. package/src/strategies/goals/hide-top.ts +197 -197
  148. package/src/strategies/goals/keep-away-goal.ts +221 -221
  149. package/src/strategies/goals/kill-frenzy-top.ts +80 -80
  150. package/src/strategies/goals/kill-lone-top.ts +160 -160
  151. package/src/strategies/goals/kill-target-goal.ts +59 -59
  152. package/src/strategies/goals/kill-target-top.ts +109 -109
  153. package/src/strategies/goals/leaf-goal.ts +27 -27
  154. package/src/strategies/goals/linger-corpse-goal.ts +35 -35
  155. package/src/strategies/goals/lone-kill-core.ts +82 -82
  156. package/src/strategies/goals/lone-kill-goal.ts +24 -24
  157. package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
  158. package/src/strategies/goals/lone-kill-task-top.ts +133 -133
  159. package/src/strategies/goals/move-room-goal.ts +60 -60
  160. package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
  161. package/src/strategies/goals/normal-shrimp-top.ts +242 -242
  162. package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
  163. package/src/strategies/goals/paradise-fish-top.ts +224 -224
  164. package/src/strategies/goals/patrol-top.ts +57 -57
  165. package/src/strategies/goals/report-patrol-top.ts +80 -80
  166. package/src/strategies/goals/safe-task-goal.ts +102 -102
  167. package/src/strategies/goals/social-task-top.ts +161 -161
  168. package/src/strategies/goals/task-kill-report-top.ts +163 -163
  169. package/src/strategies/goals/task-only-top.ts +57 -57
  170. package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
  171. package/src/strategies/goals/task-report-top.ts +57 -57
  172. package/src/strategies/goals/wander-task-goal.ts +33 -33
  173. package/src/strategies/goals/warrior-shrimp-top.test.ts +87 -87
  174. package/src/strategies/goals/warrior-shrimp-top.ts +267 -267
  175. package/src/strategies/greeting.ts +53 -53
  176. package/src/strategies/hide-spots.ts +59 -59
  177. package/src/strategies/hide.ts +24 -24
  178. package/src/strategies/kill-frenzy.ts +13 -13
  179. package/src/strategies/kill-lone.knowledge.md +17 -17
  180. package/src/strategies/kill-lone.ts +14 -14
  181. package/src/strategies/kill-target.ts +19 -19
  182. package/src/strategies/loader.test.ts +678 -678
  183. package/src/strategies/loader.ts +179 -179
  184. package/src/strategies/lone-kill-task.ts +22 -22
  185. package/src/strategies/meeting-gate.test.ts +59 -59
  186. package/src/strategies/meeting-gate.ts +23 -23
  187. package/src/strategies/move-room.ts +16 -16
  188. package/src/strategies/new-events-backfill.ts +98 -98
  189. package/src/strategies/off-route-points.ts +105 -105
  190. package/src/strategies/paradise-fish.knowledge.md +19 -19
  191. package/src/strategies/paradise-fish.ts +26 -26
  192. package/src/strategies/pathfind/distance-field.ts +150 -150
  193. package/src/strategies/pathfind/escape-planner.test.ts +197 -197
  194. package/src/strategies/pathfind/escape-planner.ts +355 -355
  195. package/src/strategies/pathfind/walkable-grid.ts +117 -117
  196. package/src/strategies/patrol.ts +12 -12
  197. package/src/strategies/player-targets.ts +13 -13
  198. package/src/strategies/report-patrol.ts +12 -12
  199. package/src/strategies/shrimp-memory.knowledge.md +19 -19
  200. package/src/strategies/shrimp-memory.ts +26 -26
  201. package/src/strategies/social-task.test.ts +28 -28
  202. package/src/strategies/social-task.ts +50 -50
  203. package/src/strategies/spawn.ts +82 -82
  204. package/src/strategies/speech-module.ts +123 -123
  205. package/src/strategies/strategy-loop.test.ts +15 -0
  206. package/src/strategies/strategy-loop.ts +776 -771
  207. package/src/strategies/task-kill-report.ts +18 -18
  208. package/src/strategies/task-only.ts +12 -12
  209. package/src/strategies/task-report.ts +23 -23
  210. package/src/strategies/types.ts +109 -109
  211. package/src/strategies/warrior-memory.knowledge.md +21 -21
  212. package/src/strategies/warrior-memory.ts +17 -17
@@ -1,242 +1,242 @@
1
- import type { GameState, PlayerInfo } from '../../sdk/types.js';
2
- import {
3
- corpseAtScene,
4
- dist,
5
- firstAvailableTask,
6
- hasKnownCorpse,
7
- isKnowledgeHostile,
8
- isKnowledgeTrusted,
9
- nonTeammatesVisible,
10
- PROGRESS_INTERVAL_MS,
11
- reportCorpseDecision,
12
- SHRIMP_VISION_RANGE,
13
- SHRIMP_VISION_RELEASE_RANGE,
14
- taskMoveDecision,
15
- } from '../game-utils.js';
16
- import type { BehaviorDecision, StrategyContext } from '../types.js';
17
- import { GreetingTracker } from '../greeting.js';
18
- import { Goal } from './goal.js';
19
- import { KeepAwayGoal, type KeepAwayGoalOptions, type ThreatResolver } from './keep-away-goal.js';
20
- import { SafeTaskOrPatrolGoal } from './safe-task-goal.js';
21
-
22
- const SUB_PRIORITY = 0.5;
23
- /** 陌生人贴近到这个距离才触发拉开(B 档入口)。 */
24
- const STRANGER_TRIGGER_RADIUS = SHRIMP_VISION_RANGE;
25
- /** B 档 KeepAwayGoal 的交战兼结束半径:270 进 / 300 出,天然滞回。 */
26
- const STRANGER_RELEASE_RADIUS = SHRIMP_VISION_RELEASE_RANGE;
27
- /** 目击者记忆 TTL;0 表示只按当前轮可见玩家判断。 */
28
- const WITNESS_MEMORY_MS = 0;
29
- /** 近期见过这么多个陌生人即豁免 B 档——蟹不敢当众动手,独处单个陌生人才危险。 */
30
- const WITNESS_EXEMPT_COUNT = 2;
31
- const TASK_RETURN_INERTIA_MS = 2_000;
32
- const FLEE_BAD_KEY = 'shrimp-flee-bad';
33
- const STRANGER_KEY = 'shrimp-stranger-distance';
34
-
35
- type Tier = 'flee-bad' | 'keep-distance' | 'task';
36
-
37
- /**
38
- * 普通虾·记忆进阶版(好人、不带刀)的核心 Goal,按行为表逐条判断,命中即执行:
39
- *
40
- * - P0 发现尸体:一律报警(reportCorpseDecision),必要时提示 Agent 更新尸体旁玩家记忆。
41
- * - P1 规避危险:
42
- * · A 视野里有坏人(知识库 hostile)→ 寻路躲避(KeepAwayGoal,不限距离)。
43
- * · B 无可信同伴且被怀疑者(未标记 = 默认被怀疑)贴近(≤270px)→ 先拉开距离(KeepAwayGoal,拉开到 300px 即回任务)。
44
- * 目击者豁免:当前见到 ≥2 个陌生人时不触发后撤(蟹不敢当众动手,独处才危险);
45
- * 进行中的后撤也会重新判定,只有仍是单陌生人危险时才拉满到 300px。豁免期间陌生人也不算任务威胁点。
46
- * - P2 紧急任务:无危险时优先处理。
47
- * - P3 否则做任务 / 巡逻(SafeTaskOrPatrolGoal:测地最近、威胁旁/途经威胁的任务硬排除、带粘性)。
48
- *
49
- * 「认定坏人」「可信同伴」全部来自 ctx.knowledge(见 shrimp-memory.knowledge.md)。
50
- * 队友由游戏事实判定(nonTeammatesVisible)。有可信同伴在场时不对陌生人触发 B 档。
51
- */
52
- export class NormalShrimpTop extends Goal {
53
- private readonly taskGoal = new SafeTaskOrPatrolGoal(
54
- // 豁免期间陌生人不算威胁点(与 B 档不触发保持视野半径对齐),认定坏人始终算。
55
- (s, c) => nonTeammatesVisible(s, c)
56
- .filter(p => !this.isTrusted(p, c))
57
- .filter(p => this.isHostile(p, c) || !this.witnessExempt)
58
- .map(p => ({ x: p.x, y: p.y })),
59
- );
60
- private readonly strangerSeenAt = new Map<string, number>();
61
- private witnessExempt = false;
62
- private readonly corpseWitnessNotified = new Set<string>();
63
- private readonly greeting: GreetingTracker;
64
-
65
- constructor(greetingPhrases: string[] = []) {
66
- super();
67
- this.greeting = new GreetingTracker(greetingPhrases);
68
- }
69
-
70
- tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
71
- const visible = nonTeammatesVisible(state, ctx);
72
- this.updateWitnessMemory(visible, ctx);
73
- const decisions: BehaviorDecision[] = [];
74
- const greeting = this.greeting.tryGreeting(state);
75
- if (greeting) decisions.push(greeting);
76
-
77
- // ── P0 发现尸体 → 报警 ──
78
- if (hasKnownCorpse(ctx)) {
79
- // 「尸体旁目击者」只在此刻确实站在命案现场(当帧视野有尸体)时才标注——否则会把后来路上
80
- // 遇见的人误报成目击者。报尸本身可以追着记忆里已离开视野的尸体走回去。
81
- if (corpseAtScene(state)) this.notifyCorpseWitnesses(visible, ctx);
82
- else this.corpseWitnessNotified.clear();
83
- const report = reportCorpseDecision(state, ctx, { respectBlock: true });
84
- if (report) {
85
- this.clearSub();
86
- return [report, ...decisions];
87
- }
88
- // 报告短暂受阻时继续执行后续分支,避免在尸体旁空站。
89
- } else {
90
- this.corpseWitnessNotified.clear();
91
- }
92
-
93
- // ── P1-A 视野里有认定坏人 → 寻路躲避 ──
94
- const threats = visible.filter(p => this.isHostile(p, ctx));
95
- if (threats.length > 0) {
96
- this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
97
- this.setKeepAway(FLEE_BAD_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => this.isHostile(p, c)), {
98
- threatRadius: Infinity,
99
- noun: '认定坏人',
100
- });
101
- this.emitProgress(state, ctx, visible, 'flee-bad');
102
- return decisions;
103
- }
104
-
105
- // ── P1-B 无可信同伴且陌生人贴近 → 拉开距离再做任务 ──
106
- const trustedVisible = visible.some(p => this.isTrusted(p, ctx));
107
- if (!trustedVisible) {
108
- const you = state.you;
109
- const strangerClose = visible.some(p =>
110
- !this.isTrusted(p, ctx) && (p.distance ?? dist(p.x, p.y, you.x, you.y)) <= STRANGER_TRIGGER_RADIUS);
111
- const worker = this.subGoal;
112
- const stillBackingOff = worker instanceof KeepAwayGoal && worker.key === STRANGER_KEY && !worker.isFinish(state, ctx);
113
- // 第二个陌生人加入时重新判定,豁免生效后不继续旧的后撤。
114
- if (!this.witnessExempt && (strangerClose || stillBackingOff)) {
115
- this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
116
- this.setKeepAway(STRANGER_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => !this.isTrusted(p, c)), {
117
- threatRadius: STRANGER_RELEASE_RADIUS,
118
- noun: '陌生人',
119
- planOpts: { steps: 4 },
120
- });
121
- this.emitProgress(state, ctx, visible, 'keep-distance');
122
- return decisions;
123
- }
124
- }
125
-
126
- // ── P2 紧急任务 ──
127
- const emergency = firstAvailableTask([], t => t.faction !== 'crab', ctx.emergency, ctx.blockedMoveTarget);
128
- if (emergency) {
129
- this.clearSub();
130
- return [taskMoveDecision(state, ctx, emergency), ...decisions];
131
- }
132
-
133
- // ── P3 兜底 → 做任务 / 巡逻 ──
134
- this.clearSub();
135
- const work = this.taskGoal.tick(state, ctx);
136
- this.emitProgress(state, ctx, visible, 'task');
137
- return [...work, ...decisions];
138
- }
139
-
140
- /**
141
- * 目击者豁免:用 TTL 记忆平滑陌生人计数;当前设为 0,只看本轮可见玩家。
142
- */
143
- private updateWitnessMemory(visible: PlayerInfo[], ctx: StrategyContext): void {
144
- const now = Date.now();
145
- for (const p of visible) {
146
- if (this.isTrusted(p, ctx)) continue;
147
- const key = p.name || String(p.seat ?? '');
148
- if (key) this.strangerSeenAt.set(key, now);
149
- }
150
- for (const [key, at] of this.strangerSeenAt) {
151
- if (now - at > WITNESS_MEMORY_MS) this.strangerSeenAt.delete(key);
152
- }
153
- this.witnessExempt = this.strangerSeenAt.size >= WITNESS_EXEMPT_COUNT;
154
- }
155
-
156
- /** 坏人:知识库 hostile(明确敌对)。被怀疑者(未标记)不在此列,走 B 档保持距离。 */
157
- private isHostile(p: PlayerInfo, ctx: StrategyContext): boolean {
158
- return isKnowledgeHostile(p, ctx);
159
- }
160
-
161
- /** 可信同伴:知识库 trusted。 */
162
- private isTrusted(p: PlayerInfo, ctx: StrategyContext): boolean {
163
- return isKnowledgeTrusted(p, ctx);
164
- }
165
-
166
- private setKeepAway(key: string, resolve: ThreatResolver, options: KeepAwayGoalOptions): void {
167
- const worker = this.subGoal;
168
- if (worker instanceof KeepAwayGoal && worker.key === key) return;
169
- if (this.subGoal) this.removeSubGoal();
170
- this.setSubGoal(new KeepAwayGoal(key, resolve, options), SUB_PRIORITY);
171
- }
172
-
173
- private clearSub(): void {
174
- if (this.subGoal) this.removeSubGoal();
175
- }
176
-
177
- private notifyCorpseWitnesses(visible: PlayerInfo[], ctx: StrategyContext): void {
178
- const bad: string[] = [];
179
- const paradise: string[] = [];
180
- const unknown: string[] = [];
181
- const trusted: string[] = [];
182
-
183
- for (const player of visible) {
184
- const key = player.name || String(player.seat ?? '');
185
- if (!key || this.corpseWitnessNotified.has(key)) continue;
186
- this.corpseWitnessNotified.add(key);
187
-
188
- if (this.isHostile(player, ctx)) bad.push(player.name);
189
- else if (this.isKnownParadiseFish(player, ctx)) paradise.push(player.name);
190
- else if (this.isTrusted(player, ctx)) trusted.push(player.name);
191
- else unknown.push(player.name);
192
- }
193
-
194
- if (bad.length > 0) ctx.notifications.push(`尸体旁发现认定坏人${bad.join('、')},直接报警。`);
195
- if (paradise.length > 0) ctx.notifications.push(`尸体旁发现天堂鱼${paradise.join('、')},直接报警。`);
196
- if (unknown.length > 0) ctx.notifications.push(`尸体旁发现身份不明者${unknown.join('、')},直接报警;建议将其记忆为坏人。`);
197
- if (trusted.length > 0) ctx.notifications.push(`尸体旁发现可信同伴${trusted.join('、')},仍直接报警;建议重新怀疑并复核记忆。`);
198
- }
199
-
200
- private isKnownParadiseFish(p: PlayerInfo, ctx: StrategyContext): boolean {
201
- const role = ctx.knowledge?.roleOf(p);
202
- return role === 'neutral_paradise_fish'
203
- || role === 'paradise_fish'
204
- || role === '天堂鱼'
205
- || (ctx.knowledge?.hasTag(p, 'paradise_fish') ?? false);
206
- }
207
-
208
- private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: Tier): void {
209
- const now = Date.now();
210
- if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
211
- ctx.lastProgressNotifyAt = now;
212
-
213
- const room = state.you.room ?? '未知';
214
- const completed = ctx.taskData.filter(t => t.status === 'completed').length;
215
- const total = ctx.taskData.filter(t => t.faction !== 'crab').length;
216
- const current = this.taskGoal.currentTask;
217
-
218
- let msg = `[进度] 当前在${room}`;
219
- if (tier === 'flee-bad') {
220
- const names = visible.filter(p => this.isHostile(p, ctx)).map(p => p.name).join('、');
221
- msg += `,发现认定坏人${names},正在远离。`;
222
- } else if (tier === 'keep-distance') {
223
- msg += `,陌生人靠得太近,先拉开距离再做任务。`;
224
- } else if (visible.length === 0) {
225
- msg += ',附近无人,做任务/巡逻。';
226
- } else if (visible.some(p => this.isTrusted(p, ctx))) {
227
- msg += `,附近有可信同伴,结伴做任务。`;
228
- } else if (this.witnessExempt && visible.length >= 2) {
229
- msg += `,附近${visible.length}个陌生人互为目击者,照常做任务。`;
230
- } else {
231
- msg += `,附近有${visible.length}人,继续做任务/巡逻。`;
232
- }
233
- if (current) {
234
- msg += ` 当前任务目标:${current.room}「${current.task_name}」。`;
235
- }
236
- msg += ` 已完成 ${completed}/${total} 个任务。`;
237
- if (state.emergency) {
238
- msg += ` 紧急任务进行中!`;
239
- }
240
- ctx.notifications.push(msg);
241
- }
242
- }
1
+ import type { GameState, PlayerInfo } from '../../sdk/types.js';
2
+ import {
3
+ corpseAtScene,
4
+ dist,
5
+ firstAvailableTask,
6
+ hasKnownCorpse,
7
+ isKnowledgeHostile,
8
+ isKnowledgeTrusted,
9
+ nonTeammatesVisible,
10
+ PROGRESS_INTERVAL_MS,
11
+ reportCorpseDecision,
12
+ SHRIMP_VISION_RANGE,
13
+ SHRIMP_VISION_RELEASE_RANGE,
14
+ taskMoveDecision,
15
+ } from '../game-utils.js';
16
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
17
+ import { GreetingTracker } from '../greeting.js';
18
+ import { Goal } from './goal.js';
19
+ import { KeepAwayGoal, type KeepAwayGoalOptions, type ThreatResolver } from './keep-away-goal.js';
20
+ import { SafeTaskOrPatrolGoal } from './safe-task-goal.js';
21
+
22
+ const SUB_PRIORITY = 0.5;
23
+ /** 陌生人贴近到这个距离才触发拉开(B 档入口)。 */
24
+ const STRANGER_TRIGGER_RADIUS = SHRIMP_VISION_RANGE;
25
+ /** B 档 KeepAwayGoal 的交战兼结束半径:270 进 / 300 出,天然滞回。 */
26
+ const STRANGER_RELEASE_RADIUS = SHRIMP_VISION_RELEASE_RANGE;
27
+ /** 目击者记忆 TTL;0 表示只按当前轮可见玩家判断。 */
28
+ const WITNESS_MEMORY_MS = 0;
29
+ /** 近期见过这么多个陌生人即豁免 B 档——蟹不敢当众动手,独处单个陌生人才危险。 */
30
+ const WITNESS_EXEMPT_COUNT = 2;
31
+ const TASK_RETURN_INERTIA_MS = 2_000;
32
+ const FLEE_BAD_KEY = 'shrimp-flee-bad';
33
+ const STRANGER_KEY = 'shrimp-stranger-distance';
34
+
35
+ type Tier = 'flee-bad' | 'keep-distance' | 'task';
36
+
37
+ /**
38
+ * 普通虾·记忆进阶版(好人、不带刀)的核心 Goal,按行为表逐条判断,命中即执行:
39
+ *
40
+ * - P0 发现尸体:一律报警(reportCorpseDecision),必要时提示 Agent 更新尸体旁玩家记忆。
41
+ * - P1 规避危险:
42
+ * · A 视野里有坏人(知识库 hostile)→ 寻路躲避(KeepAwayGoal,不限距离)。
43
+ * · B 无可信同伴且被怀疑者(未标记 = 默认被怀疑)贴近(≤270px)→ 先拉开距离(KeepAwayGoal,拉开到 300px 即回任务)。
44
+ * 目击者豁免:当前见到 ≥2 个陌生人时不触发后撤(蟹不敢当众动手,独处才危险);
45
+ * 进行中的后撤也会重新判定,只有仍是单陌生人危险时才拉满到 300px。豁免期间陌生人也不算任务威胁点。
46
+ * - P2 紧急任务:无危险时优先处理。
47
+ * - P3 否则做任务 / 巡逻(SafeTaskOrPatrolGoal:测地最近、威胁旁/途经威胁的任务硬排除、带粘性)。
48
+ *
49
+ * 「认定坏人」「可信同伴」全部来自 ctx.knowledge(见 shrimp-memory.knowledge.md)。
50
+ * 队友由游戏事实判定(nonTeammatesVisible)。有可信同伴在场时不对陌生人触发 B 档。
51
+ */
52
+ export class NormalShrimpTop extends Goal {
53
+ private readonly taskGoal = new SafeTaskOrPatrolGoal(
54
+ // 豁免期间陌生人不算威胁点(与 B 档不触发保持视野半径对齐),认定坏人始终算。
55
+ (s, c) => nonTeammatesVisible(s, c)
56
+ .filter(p => !this.isTrusted(p, c))
57
+ .filter(p => this.isHostile(p, c) || !this.witnessExempt)
58
+ .map(p => ({ x: p.x, y: p.y })),
59
+ );
60
+ private readonly strangerSeenAt = new Map<string, number>();
61
+ private witnessExempt = false;
62
+ private readonly corpseWitnessNotified = new Set<string>();
63
+ private readonly greeting: GreetingTracker;
64
+
65
+ constructor(greetingPhrases: string[] = []) {
66
+ super();
67
+ this.greeting = new GreetingTracker(greetingPhrases);
68
+ }
69
+
70
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
71
+ const visible = nonTeammatesVisible(state, ctx);
72
+ this.updateWitnessMemory(visible, ctx);
73
+ const decisions: BehaviorDecision[] = [];
74
+ const greeting = this.greeting.tryGreeting(state);
75
+ if (greeting) decisions.push(greeting);
76
+
77
+ // ── P0 发现尸体 → 报警 ──
78
+ if (hasKnownCorpse(ctx)) {
79
+ // 「尸体旁目击者」只在此刻确实站在命案现场(当帧视野有尸体)时才标注——否则会把后来路上
80
+ // 遇见的人误报成目击者。报尸本身可以追着记忆里已离开视野的尸体走回去。
81
+ if (corpseAtScene(state)) this.notifyCorpseWitnesses(visible, ctx);
82
+ else this.corpseWitnessNotified.clear();
83
+ const report = reportCorpseDecision(state, ctx, { respectBlock: true });
84
+ if (report) {
85
+ this.clearSub();
86
+ return [report, ...decisions];
87
+ }
88
+ // 报告短暂受阻时继续执行后续分支,避免在尸体旁空站。
89
+ } else {
90
+ this.corpseWitnessNotified.clear();
91
+ }
92
+
93
+ // ── P1-A 视野里有认定坏人 → 寻路躲避 ──
94
+ const threats = visible.filter(p => this.isHostile(p, ctx));
95
+ if (threats.length > 0) {
96
+ this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
97
+ this.setKeepAway(FLEE_BAD_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => this.isHostile(p, c)), {
98
+ threatRadius: Infinity,
99
+ noun: '认定坏人',
100
+ });
101
+ this.emitProgress(state, ctx, visible, 'flee-bad');
102
+ return decisions;
103
+ }
104
+
105
+ // ── P1-B 无可信同伴且陌生人贴近 → 拉开距离再做任务 ──
106
+ const trustedVisible = visible.some(p => this.isTrusted(p, ctx));
107
+ if (!trustedVisible) {
108
+ const you = state.you;
109
+ const strangerClose = visible.some(p =>
110
+ !this.isTrusted(p, ctx) && (p.distance ?? dist(p.x, p.y, you.x, you.y)) <= STRANGER_TRIGGER_RADIUS);
111
+ const worker = this.subGoal;
112
+ const stillBackingOff = worker instanceof KeepAwayGoal && worker.key === STRANGER_KEY && !worker.isFinish(state, ctx);
113
+ // 第二个陌生人加入时重新判定,豁免生效后不继续旧的后撤。
114
+ if (!this.witnessExempt && (strangerClose || stillBackingOff)) {
115
+ this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
116
+ this.setKeepAway(STRANGER_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => !this.isTrusted(p, c)), {
117
+ threatRadius: STRANGER_RELEASE_RADIUS,
118
+ noun: '陌生人',
119
+ planOpts: { steps: 4 },
120
+ });
121
+ this.emitProgress(state, ctx, visible, 'keep-distance');
122
+ return decisions;
123
+ }
124
+ }
125
+
126
+ // ── P2 紧急任务 ──
127
+ const emergency = firstAvailableTask([], t => t.faction !== 'crab', ctx.emergency, ctx.blockedMoveTarget);
128
+ if (emergency) {
129
+ this.clearSub();
130
+ return [taskMoveDecision(state, ctx, emergency), ...decisions];
131
+ }
132
+
133
+ // ── P3 兜底 → 做任务 / 巡逻 ──
134
+ this.clearSub();
135
+ const work = this.taskGoal.tick(state, ctx);
136
+ this.emitProgress(state, ctx, visible, 'task');
137
+ return [...work, ...decisions];
138
+ }
139
+
140
+ /**
141
+ * 目击者豁免:用 TTL 记忆平滑陌生人计数;当前设为 0,只看本轮可见玩家。
142
+ */
143
+ private updateWitnessMemory(visible: PlayerInfo[], ctx: StrategyContext): void {
144
+ const now = Date.now();
145
+ for (const p of visible) {
146
+ if (this.isTrusted(p, ctx)) continue;
147
+ const key = p.name || String(p.seat ?? '');
148
+ if (key) this.strangerSeenAt.set(key, now);
149
+ }
150
+ for (const [key, at] of this.strangerSeenAt) {
151
+ if (now - at > WITNESS_MEMORY_MS) this.strangerSeenAt.delete(key);
152
+ }
153
+ this.witnessExempt = this.strangerSeenAt.size >= WITNESS_EXEMPT_COUNT;
154
+ }
155
+
156
+ /** 坏人:知识库 hostile(明确敌对)。被怀疑者(未标记)不在此列,走 B 档保持距离。 */
157
+ private isHostile(p: PlayerInfo, ctx: StrategyContext): boolean {
158
+ return isKnowledgeHostile(p, ctx);
159
+ }
160
+
161
+ /** 可信同伴:知识库 trusted。 */
162
+ private isTrusted(p: PlayerInfo, ctx: StrategyContext): boolean {
163
+ return isKnowledgeTrusted(p, ctx);
164
+ }
165
+
166
+ private setKeepAway(key: string, resolve: ThreatResolver, options: KeepAwayGoalOptions): void {
167
+ const worker = this.subGoal;
168
+ if (worker instanceof KeepAwayGoal && worker.key === key) return;
169
+ if (this.subGoal) this.removeSubGoal();
170
+ this.setSubGoal(new KeepAwayGoal(key, resolve, options), SUB_PRIORITY);
171
+ }
172
+
173
+ private clearSub(): void {
174
+ if (this.subGoal) this.removeSubGoal();
175
+ }
176
+
177
+ private notifyCorpseWitnesses(visible: PlayerInfo[], ctx: StrategyContext): void {
178
+ const bad: string[] = [];
179
+ const paradise: string[] = [];
180
+ const unknown: string[] = [];
181
+ const trusted: string[] = [];
182
+
183
+ for (const player of visible) {
184
+ const key = player.name || String(player.seat ?? '');
185
+ if (!key || this.corpseWitnessNotified.has(key)) continue;
186
+ this.corpseWitnessNotified.add(key);
187
+
188
+ if (this.isHostile(player, ctx)) bad.push(player.name);
189
+ else if (this.isKnownParadiseFish(player, ctx)) paradise.push(player.name);
190
+ else if (this.isTrusted(player, ctx)) trusted.push(player.name);
191
+ else unknown.push(player.name);
192
+ }
193
+
194
+ if (bad.length > 0) ctx.notifications.push(`尸体旁发现认定坏人${bad.join('、')},直接报警。`);
195
+ if (paradise.length > 0) ctx.notifications.push(`尸体旁发现天堂鱼${paradise.join('、')},直接报警。`);
196
+ if (unknown.length > 0) ctx.notifications.push(`尸体旁发现身份不明者${unknown.join('、')},直接报警;建议将其记忆为坏人。`);
197
+ if (trusted.length > 0) ctx.notifications.push(`尸体旁发现可信同伴${trusted.join('、')},仍直接报警;建议重新怀疑并复核记忆。`);
198
+ }
199
+
200
+ private isKnownParadiseFish(p: PlayerInfo, ctx: StrategyContext): boolean {
201
+ const role = ctx.knowledge?.roleOf(p);
202
+ return role === 'neutral_paradise_fish'
203
+ || role === 'paradise_fish'
204
+ || role === '天堂鱼'
205
+ || (ctx.knowledge?.hasTag(p, 'paradise_fish') ?? false);
206
+ }
207
+
208
+ private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: Tier): void {
209
+ const now = Date.now();
210
+ if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
211
+ ctx.lastProgressNotifyAt = now;
212
+
213
+ const room = state.you.room ?? '未知';
214
+ const completed = ctx.taskData.filter(t => t.status === 'completed').length;
215
+ const total = ctx.taskData.filter(t => t.faction !== 'crab').length;
216
+ const current = this.taskGoal.currentTask;
217
+
218
+ let msg = `[进度] 当前在${room}`;
219
+ if (tier === 'flee-bad') {
220
+ const names = visible.filter(p => this.isHostile(p, ctx)).map(p => p.name).join('、');
221
+ msg += `,发现认定坏人${names},正在远离。`;
222
+ } else if (tier === 'keep-distance') {
223
+ msg += `,陌生人靠得太近,先拉开距离再做任务。`;
224
+ } else if (visible.length === 0) {
225
+ msg += ',附近无人,做任务/巡逻。';
226
+ } else if (visible.some(p => this.isTrusted(p, ctx))) {
227
+ msg += `,附近有可信同伴,结伴做任务。`;
228
+ } else if (this.witnessExempt && visible.length >= 2) {
229
+ msg += `,附近${visible.length}个陌生人互为目击者,照常做任务。`;
230
+ } else {
231
+ msg += `,附近有${visible.length}人,继续做任务/巡逻。`;
232
+ }
233
+ if (current) {
234
+ msg += ` 当前任务目标:${current.room}「${current.task_name}」。`;
235
+ }
236
+ msg += ` 已完成 ${completed}/${total} 个任务。`;
237
+ if (state.emergency) {
238
+ msg += ` 紧急任务进行中!`;
239
+ }
240
+ ctx.notifications.push(msg);
241
+ }
242
+ }