@myclaw163/clawclaw-cli 0.6.67 → 0.6.68

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 (40) hide show
  1. package/bin/clawclaw-cli.mjs +3 -3
  2. package/package.json +1 -1
  3. package/scripts/sync-bundled-skill.mjs +1 -1
  4. package/skills/clawclaw/references/COMMANDS.md +4 -4
  5. package/skills/clawclaw/references/KNOWLEDGE.md +14 -12
  6. package/src/commands/config.ts +30 -30
  7. package/src/commands/knowledge.test.ts +4 -10
  8. package/src/commands/knowledge.ts +10 -39
  9. package/src/commands/setup/hermes.test.ts +96 -96
  10. package/src/commands/setup/hermes.ts +76 -76
  11. package/src/commands/setup/index.ts +13 -13
  12. package/src/commands/setup/openclaw.test.ts +114 -114
  13. package/src/commands/setup/openclaw.ts +147 -147
  14. package/src/lib/host-config-patcher.test.ts +130 -130
  15. package/src/lib/host-config-patcher.ts +151 -151
  16. package/src/lib/hub-reminder.ts +19 -19
  17. package/src/lib/knowledge-store.test.ts +28 -38
  18. package/src/lib/knowledge-store.ts +52 -57
  19. package/src/sdk/index.ts +2 -3
  20. package/src/sdk/types.ts +2 -0
  21. package/src/strategies/avoid-players.knowledge.md +7 -8
  22. package/src/strategies/avoid-players.ts +1 -1
  23. package/src/strategies/corpse-patrol.ts +1 -1
  24. package/src/strategies/game-utils.ts +29 -17
  25. package/src/strategies/goals/avoid-players-top.ts +3 -3
  26. package/src/strategies/goals/corpse-patrol-top.ts +23 -1
  27. package/src/strategies/goals/leaf-goal.ts +2 -0
  28. package/src/strategies/goals/lone-kill-task-top.ts +39 -8
  29. package/src/strategies/goals/normal-shrimp-top.ts +11 -11
  30. package/src/strategies/goals/paradise-fish-top.ts +32 -15
  31. package/src/strategies/goals/warrior-shrimp-top.test.ts +4 -3
  32. package/src/strategies/goals/warrior-shrimp-top.ts +140 -80
  33. package/src/strategies/kill-lone.knowledge.md +6 -9
  34. package/src/strategies/lone-kill-task.ts +1 -1
  35. package/src/strategies/paradise-fish.knowledge.md +7 -8
  36. package/src/strategies/paradise-fish.ts +1 -1
  37. package/src/strategies/shrimp-memory.knowledge.md +7 -8
  38. package/src/strategies/shrimp-memory.ts +1 -1
  39. package/src/strategies/warrior-memory.knowledge.md +9 -10
  40. package/src/strategies/warrior-memory.ts +1 -1
@@ -9,7 +9,6 @@ import {
9
9
  firstAvailableTask,
10
10
  hasKnownCorpse,
11
11
  isKnowledgeHostile,
12
- isKnowledgeThreat,
13
12
  isKnowledgeTrusted,
14
13
  killCooldownSecs,
15
14
  killCommitRange,
@@ -36,22 +35,31 @@ const WITNESS_EXEMPT_COUNT = 2;
36
35
  const TASK_RETURN_INERTIA_MS = 2_000;
37
36
  const FLEE_KEY = 'warrior-flee';
38
37
  const STRANGER_KEY = 'warrior-stranger-distance';
39
- // 自卫总开关:false=遇到 suspect 一律只回避(绝不变向试探、绝不自卫出刀),等同纯回避档;true=启用下方绝境自卫流程。
40
- const SELF_DEFENSE_ENABLED: boolean = false;
38
+ // 尸体现场灭口开关:false=站在尸体旁也绝不因「身边有未报警的身份不明者」先手灭口,只报警(hostile/启动目标仍按 P1 追杀);
39
+ // true=刀可用时先击杀尸体旁最近的非 trusted 非队友。关掉是为根治「把尸体边未报警的无辜虾当凶手灭口」的误杀。
40
+ const CORPSE_SCENE_KILL_ENABLED: boolean = false;
41
+ // 自卫总开关:false=遇到被怀疑者一律只回避(绝不变向试探、绝不自卫出刀),等同纯回避档;true=启用下方绝境自卫流程。
42
+ const SELF_DEFENSE_ENABLED: boolean = true;
41
43
  // 自卫出刀只能由「主动变向试探 + 验证对方跟随」达成,绝不靠被动转角/方位判定——那些会被走廊弯曲、寻路曲线、
42
44
  // 贴身掠过污染(对方只是顺着走廊转、并非在追我,却累积出大转角)。流程:
43
- // ① 同一 suspect 连续 JUKE_STREAK 帧「朝我贴身追击」(aimedPursuit:相对上帧位移 ≥ MOVE_EPS 且方向与「指向我」
45
+ // ① 同一被怀疑者连续 JUKE_STREAK 帧「朝我贴身追击」(aimedPursuit:相对上帧位移 ≥ MOVE_EPS 且方向与「指向我」
44
46
  // 夹角余弦 ≥ TOWARD_COS;cone 半角≈31.8°)且 planEscape 判定甩不掉 → 被咬住又逃不掉,开始试探;
45
47
  // ② 主动横向变一次向(jukeTarget),变向幅度足够大,使「不跟随者」必然掉出 cone;
46
- // ③ 变向后 JUKE_WINDOW 帧内,若它重新指向我的【新】位置(cone re-entry)= 跟随一次。只认变向【之后】的重新瞄准,
47
- // 走廊弯曲转过的角度一概不算——直奔固定任务点 / 顺着走廊走的虾不会跟我的横向变向,永远掉出 cone、攒不到跟随。
48
- // 变向后第 1 帧对方因反应延迟可能短暂掉出 cone,故用 WINDOW 容忍这一帧;窗口内始终不重新瞄准 = 没跟 → 重置。
48
+ // ③ 变向后 JUKE_WINDOW 帧内,比较对方【变向前朝向 hBefore】与【当前朝向 hNow】:朝向变化(hNow−hBefore)沿我 juke
49
+ // 方向偏转 reaim≥MARGIN、且当前朝向仍对准我新位置 = 跟随一次(jukeFollowed)。只认对方朝向【响应我变向】的再
50
+ // 瞄准——同向匀速通勤的虾朝向恒定(hNow≈hBefore,reaim≈0),无论它是否恰好直行掠过我旧位置都攒不到跟随;走廊弯
51
+ // 曲/几何发散一概不算。变向后第 1 帧对方因反应延迟可能短暂未转向,故用 WINDOW 容忍这一帧;窗口内始终不跟 → 重置。
49
52
  // ④ 攒够 JUKE_FOLLOWS 次跟随、且已进 killRangeFor 射程、视线无墙,才出刀(趁 160 射程在合拢阶段先手,蟹只有 80)。
50
53
  const SELF_DEFENSE_JUKE_STREAK = 2;
51
54
  const SELF_DEFENSE_JUKE_WINDOW = 3;
52
55
  const SELF_DEFENSE_JUKE_FOLLOWS = 2;
53
56
  const SELF_DEFENSE_MOVE_EPS = 40;
54
57
  const SELF_DEFENSE_TOWARD_COS = 0.85;
58
+ // 跟随判定的「再瞄准余量」:变向后对方【朝向变化】(hNow−hBefore)沿我 juke 方向的分量须达到这个余量,才算它响应了
59
+ // 我的变向。同向匀速通勤者朝向恒定、朝向变化≈0 → reaim≈0 天然被拒;真跟随者转向追我新位置、朝向变化沿 juke 方向 →
60
+ // reaim 为正(近距离 >0.2,越远越小但仍 ≫ 位置噪声)。取一个略高于噪声(≪0.01)的小正数即可分开两者;过大会误拒
61
+ // 中远距离的真跟随者、削弱真自卫。
62
+ const SELF_DEFENSE_REAIM_MARGIN = 0.05;
55
63
  // 主动变向试探:横向位移目标距离;方向以横向为主、略带「背离对方」分量以免试探时丢距离;落点至少这么远才算有效变向。
56
64
  const SELF_DEFENSE_JUKE_DIST = 170;
57
65
  const SELF_DEFENSE_JUKE_LATERAL = 0.85;
@@ -83,35 +91,42 @@ function segmentWalkable(grid: WalkableGrid, ax: number, ay: number, bx: number,
83
91
  /**
84
92
  * 带刀虾·记忆进阶版(武士虾/枪虾,带刀好人)。好人阵营但能出刀,按行为表的优先级逐条判断:
85
93
  *
86
- * - P0 发现尸体:旁边有非保护对象且刀可用先出刀;否则报警(靠近至可报距离)。
87
- * - P1 处理危险:明确猎杀目标(启动参数 / hostile)刀好就追杀;
88
- * suspect 只表示可疑,默认回避,绝不仅凭怀疑或被动观察出刀——只有主动变向试探、验证它确实跟着我变向追击且退无可退才自卫先手;
89
- * 无可信同伴且单个无标记陌生人贴近 按普通虾 B 档先拉开距离。
94
+ * - P0 发现尸体:旁边是 hostile/启动猎杀目标且刀可用 现场先手击杀;未报警的身份不明者默认不灭口
95
+ * (CORPSE_SCENE_KILL_ENABLED,根治误杀);否则报警(靠近至可报距离)。
96
+ * - P1 处理危险:坏人(启动参数 / 知识库 hostile)刀好就追杀;
97
+ * 其余非 trusted 一律按「被怀疑」处理(未标记 = 默认被怀疑,不再有 suspect 标记),默认只回避,绝不仅凭怀疑或被动观察出刀——
98
+ * 只有被同一个被怀疑者持续贴身追击、主动变向试探验证它跟着我变向咬住、退无可退才自卫先手
99
+ * (实战里真凶常没来得及标记就追杀,故对所有被怀疑者都适用,不要求先标记);坏人刀冷时硬躲所有非好人;
100
+ * 无可信同伴且单个被怀疑者贴近(未触发自卫)→ 按普通虾 B 档先拉开距离(多人互为目击者则照常做任务)。
90
101
  * - P2 紧急任务:无危险时优先处理。
91
102
  * - P3 兜底:做任务 / 巡逻
92
103
  * (SafeTaskOrPatrolGoal:测地最近、威胁旁/途经威胁的任务硬排除、带粘性)。
93
104
  *
94
- * 谁算「认定坏人」「可信同伴」来自 ctx.knowledge(见 warrior-memory.knowledge.md),
105
+ * 谁算「坏人」「可信同伴」来自 ctx.knowledge(见 warrior-memory.knowledge.md),其余默认被怀疑;
95
106
  * 队友由游戏事实判定(nonTeammatesVisible),知识库只提供推断。
96
107
  */
97
108
  export class WarriorShrimpTop extends Goal {
98
109
  private readonly taskGoal = new SafeTaskOrPatrolGoal(
99
- // 威胁点 = 当前会触发追杀/后撤的对象,或未被目击者豁免的无标记陌生人;保护对象不算。
110
+ // 威胁点 = 坏人(始终算),或未被目击者豁免的被怀疑者;可信同伴不算。
100
111
  (s, c) => nonTeammatesVisible(s, c)
101
112
  .filter(p => !this.isTrusted(p, c))
102
- .filter(p => this.isThreat(p, c) || !this.witnessExempt)
113
+ .filter(p => this.isHuntTarget(p, c) || !this.witnessExempt)
103
114
  .map(p => ({ x: p.x, y: p.y })),
104
115
  );
105
116
  private readonly strangerSeenAt = new Map<string, number>();
106
117
  private witnessExempt = false;
107
- /** 自卫:当前被判定在追我的 suspect key、它上一帧坐标、连续「朝我贴身追击」帧数(决定何时开始试探)、
118
+ /** 自卫:当前被判定在追我的被怀疑者 key、它上一帧坐标、它本帧朝向、连续「朝我贴身追击」帧数(决定何时开始试探)、
108
119
  * 变向后等待对方跟随的剩余帧数(0=未在等)、发起变向那刻我的坐标(验证跟随用)、已验证的跟随次数、
109
120
  * 本轮是否已推过自卫简报。 */
110
121
  private selfDefenseTargetKey: string | null = null;
111
- private selfDefenseSuspectPrev: { x: number; y: number } | null = null;
122
+ private selfDefensePursuerPrev: { x: number; y: number } | null = null;
123
+ /** 追击者本帧朝向(上一帧→本帧的单位位移),aimedPursuit 每帧维护;jukeFollowed 用它判定对方是否响应我变向改朝向。 */
124
+ private selfDefensePursuerHeading: { x: number; y: number } | null = null;
112
125
  private selfDefenseStreak = 0;
113
126
  private selfDefenseJukeWatch = 0;
114
127
  private selfDefenseJukeFromPos: { x: number; y: number } | null = null;
128
+ /** 发起变向那刻追击者的朝向(hBefore):jukeFollowed 比较它与变向后当前朝向的【变化】,判定对方是否真的转向追我新位置。 */
129
+ private selfDefenseJukePursuerHeading: { x: number; y: number } | null = null;
115
130
  private selfDefenseJukeFollows = 0;
116
131
  private selfDefenseAlerted = false;
117
132
 
@@ -131,10 +146,13 @@ export class WarriorShrimpTop extends Goal {
131
146
  const killReady = canUseKill(state);
132
147
 
133
148
  // ── P0 发现尸体 ──
134
- // 灭口贴脸目击者只在「此刻就在命案现场」(当帧视野有尸体)时考虑——记忆里某处有尸体不算现场,
135
- // 否则会变成随便撞见一个人就灭口。
149
+ // 只在「此刻就在命案现场」(当帧视野有尸体)时考虑现场出刀——记忆里某处有尸体不算现场。
136
150
  if (corpseAtScene(state) && killReady) {
137
- const victim = this.nearestKillable(state, ctx, visible);
151
+ // 尸体旁的【已确认敌对 / 启动猎杀目标】始终先手击杀,不受灭口开关影响、且排在报尸之前——凶手就在身边时照杀。
152
+ // 仅「未报警的身份不明者」(非 trusted、非 hostile)才受 CORPSE_SCENE_KILL_ENABLED 控制:默认关,
153
+ // 根治「把尸体边未报警的无辜虾当凶手灭口」的误杀。
154
+ const victim = this.nearestKillable(state, ctx, visible, true)
155
+ ?? (CORPSE_SCENE_KILL_ENABLED ? this.nearestKillable(state, ctx, visible, false) : null);
138
156
  if (victim) {
139
157
  this.clearSub();
140
158
  return [{ action: Action.kill(victim.name) }];
@@ -149,41 +167,45 @@ export class WarriorShrimpTop extends Goal {
149
167
  }
150
168
  }
151
169
 
152
- // ── P1-A hostile 刀好就追;suspect 只回避(避不开且能杀时自卫出刀) ──
153
- const threats = visible.filter(p => this.isThreat(p, ctx));
170
+ // ── P1 处理危险:非 trusted 即潜在威胁(坏人 + 被怀疑) ──
171
+ const threats = visible.filter(p => !this.isTrusted(p, ctx));
154
172
  if (threats.length > 0) {
155
173
  const huntTargets = threats.filter(p => this.isHuntTarget(p, ctx));
174
+ // P1-A 坏人/启动目标且刀好 → 主动追杀。
156
175
  if (killReady && huntTargets.length > 0) {
157
176
  this.resetSelfDefense();
158
177
  this.setHunt(this.nearestByDistance(state, huntTargets).name);
159
178
  return [];
160
179
  }
161
- // 只剩 suspect、本应只回避;但若连续多帧确认同一个在朝我贴身追击且避让算法判定避不开,就主动横向变向试探,
162
- // 只有它每次都跟着我的变向重新咬住(攒够跟随次数)、且已进 160 射程、视线无墙,才自卫先下手(趁 reach 优势在合拢阶段先手)+ Agent 自卫简报。
180
+ // P1-B 绝境自卫:对被怀疑的贴身追击者(非坏人、非 trusted)——连续确认朝我追击、主动变向试探它每次都跟着咬住、
181
+ // 已进 160 射程、退无可退、视线无墙,才自卫先下手(趁 reach 优势在合拢阶段先手)+ Agent 自卫简报。
182
+ // 不要求先标 suspect:实战里真凶常没来得及标记就开始追杀,所以对所有被怀疑者都适用(juke 验证保证不误杀顺路虾)。
163
183
  const selfDefense = this.selfDefenseKill(state, ctx, threats, killReady);
164
184
  if (selfDefense) return selfDefense;
165
185
 
166
- this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
167
- this.setFlee();
168
- this.emitProgress(state, ctx, visible, 'flee-bad');
169
- return [];
170
- }
171
- this.resetSelfDefense();
186
+ // P1-C 有坏人/启动目标但刀冷(杀不了)→ 硬躲所有非好人。
187
+ if (huntTargets.length > 0) {
188
+ this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
189
+ this.setFlee();
190
+ this.emitProgress(state, ctx, visible, 'flee-bad');
191
+ return [];
192
+ }
172
193
 
173
- // ── P1-B 无可信同伴且无标记陌生人贴近 → 按普通虾 B 档拉开距离 ──
174
- const trustedVisible = visible.some(p => this.isTrusted(p, ctx));
175
- if (!trustedVisible) {
194
+ // P1-D 只剩被怀疑者:无可信同伴且单个贴近、且未被目击者豁免 → 按普通虾 B 档拉开距离;
195
+ // 多个被怀疑者互为目击者(witnessExempt)则不后撤,落到下面照常做任务(自卫追踪已在 selfDefenseKill 内按需维护)。
196
+ const trustedVisible = visible.some(p => this.isTrusted(p, ctx));
176
197
  const worker = this.subGoal;
177
- const strangerClose = visible.some(p =>
178
- this.isUnmarkedStranger(p, ctx)
179
- && (p.distance ?? dist(p.x, p.y, state.you.x, state.you.y)) <= STRANGER_TRIGGER_RADIUS);
198
+ const suspectedClose = threats.some(p =>
199
+ (p.distance ?? dist(p.x, p.y, state.you.x, state.you.y)) <= STRANGER_TRIGGER_RADIUS);
180
200
  const stillBackingOff = worker instanceof KeepAwayGoal && worker.key === STRANGER_KEY && !worker.isFinish(state, ctx);
181
- if (!this.witnessExempt && (strangerClose || stillBackingOff)) {
201
+ if (!trustedVisible && !this.witnessExempt && (suspectedClose || stillBackingOff)) {
182
202
  this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
183
203
  this.setStrangerKeepAway();
184
204
  this.emitProgress(state, ctx, visible, 'keep-distance');
185
205
  return [];
186
206
  }
207
+ } else {
208
+ this.resetSelfDefense();
187
209
  }
188
210
 
189
211
  // ── P2 紧急任务 ──
@@ -199,11 +221,11 @@ export class WarriorShrimpTop extends Goal {
199
221
  return this.taskGoal.tick(state, ctx);
200
222
  }
201
223
 
202
- /** 目击者豁免:当前只按本轮可见玩家判断,见到 2 个以上陌生人时不对无标记者后撤。 */
224
+ /** 目击者豁免:当前只按本轮可见玩家判断,见到 2 个以上被怀疑者时不对其后撤(多人互为目击者,蟹不敢当众动手)。 */
203
225
  private updateWitnessMemory(visible: PlayerInfo[], ctx: StrategyContext): void {
204
226
  const now = Date.now();
205
227
  for (const p of visible) {
206
- if (!this.isUnmarkedStranger(p, ctx)) continue;
228
+ if (!this.isSuspected(p, ctx)) continue;
207
229
  const key = p.name || String(p.seat ?? '');
208
230
  if (key) this.strangerSeenAt.set(key, now);
209
231
  }
@@ -213,8 +235,9 @@ export class WarriorShrimpTop extends Goal {
213
235
  this.witnessExempt = this.strangerSeenAt.size >= WITNESS_EXEMPT_COUNT;
214
236
  }
215
237
 
216
- private isUnmarkedStranger(p: PlayerInfo, ctx: StrategyContext): boolean {
217
- return !this.isTrusted(p, ctx) && !this.isThreat(p, ctx);
238
+ /** 被怀疑者:非 trusted(好人)且非坏人/启动目标——未标记的默认就落这档,对应 B 档保持距离 + 绝境自卫。 */
239
+ private isSuspected(p: PlayerInfo, ctx: StrategyContext): boolean {
240
+ return !this.isTrusted(p, ctx) && !this.isHuntTarget(p, ctx);
218
241
  }
219
242
 
220
243
  /** 知识里 trusted 的玩家:可信同伴,永不击杀。 */
@@ -222,22 +245,28 @@ export class WarriorShrimpTop extends Goal {
222
245
  return isKnowledgeTrusted(p, ctx);
223
246
  }
224
247
 
225
- /** 明确猎杀目标:启动参数或 hostile;suspect 不在这里。 */
248
+ /** 坏人 / 明确猎杀目标:启动参数或知识库 hostile;被怀疑者不在这里。 */
226
249
  private isHuntTarget(p: PlayerInfo, ctx: StrategyContext): boolean {
227
250
  return !this.isTrusted(p, ctx)
228
251
  && (matchesAnyTarget(p, this.huntTargets, ctx) || isKnowledgeHostile(p, ctx));
229
252
  }
230
253
 
231
- /** 威胁:明确猎杀目标,或知识库 suspect 标记;trusted 永远排除。 */
254
+ /** 潜在威胁 = 任何非 trusted(坏人 + 被怀疑);刀冷遇坏人时硬躲这一整组。 */
232
255
  private isThreat(p: PlayerInfo, ctx: StrategyContext): boolean {
233
- return !this.isTrusted(p, ctx)
234
- && (this.isHuntTarget(p, ctx) || isKnowledgeThreat(p, ctx));
256
+ return !this.isTrusted(p, ctx);
235
257
  }
236
258
 
237
- /** 尸体场景下「认定坏人/身份不明」= 视野内最近的非保护非队友,且在出刀范围内。 */
238
- private nearestKillable(state: GameState, ctx: StrategyContext, visible: PlayerInfo[]): PlayerInfo | null {
259
+ /** 尸体场景出刀目标 = 视野内最近、在出刀范围内的非 trusted 非队友。hostileOnly=true 只取已确认敌对/启动猎杀目标
260
+ * (hostile / hunt 参数),用于「尸体旁敌对照杀、不受灭口开关限制」;false 含未报警的身份不明者(受开关控制)。 */
261
+ private nearestKillable(
262
+ state: GameState,
263
+ ctx: StrategyContext,
264
+ visible: PlayerInfo[],
265
+ hostileOnly: boolean,
266
+ ): PlayerInfo | null {
239
267
  return visible
240
268
  .filter(p => !this.isTrusted(p, ctx))
269
+ .filter(p => !hostileOnly || this.isHuntTarget(p, ctx))
241
270
  .map(p => ({ p, d: p.distance ?? dist(state.you.x, state.you.y, p.x, p.y) }))
242
271
  .filter(x => x.d <= killCommitRange(state.you.role))
243
272
  .sort((a, b) => a.d - b.d)[0]?.p ?? null;
@@ -250,8 +279,9 @@ export class WarriorShrimpTop extends Goal {
250
279
  }
251
280
 
252
281
  /**
253
- * 自卫击杀 / 变向试探:只处理「只剩 suspect、本应只回避」的情形(hostile 在上面已优先追杀)。出刀只能由
254
- * 「主动变向 + 验证对方跟随」达成,绝不靠被动转角/方位,避免「对方只是顺着走廊转、并非在追我」被误判。
282
+ * 自卫击杀 / 变向试探:只处理「被怀疑者」(非坏人、非 trusted)持续贴身追击的情形(坏人在上面已优先追杀/硬躲)。
283
+ * 实战里真凶往往还没被标记就开始追杀,所以这里对所有被怀疑者都适用,不要求先标 suspect——juke 验证保证只杀真在追我的。
284
+ * 出刀只能由「主动变向 + 验证对方跟随」达成,绝不靠被动转角/方位,避免「对方只是顺着走廊转、并非在追我」被误判。
255
285
  * 每帧:
256
286
  * ① aimedPursuit 判定本帧是否「朝我贴身追击」,维护连续帧数(变向等待期内的一帧不朝我是预期的反应延迟,先容忍)。
257
287
  * ② 若处在变向等待期:它重新指向我的新位置 = 跟随一次(攒一次);窗口内始终不重新瞄准 = 没跟 → 重置。
@@ -269,12 +299,18 @@ export class WarriorShrimpTop extends Goal {
269
299
  killReady: boolean,
270
300
  ): BehaviorDecision[] | null {
271
301
  if (!SELF_DEFENSE_ENABLED) return null; // 开关关闭:直接回退到「只回避」,不进入任何试探/出刀逻辑。
272
- const suspects = threats.filter(p => !this.isHuntTarget(p, ctx));
273
- if (suspects.length === 0) {
302
+ // 目击者豁免同样限制自卫:现场≥2 个互为目击者的被怀疑者时不试探、不出刀。人多时蟹不敢当众动手(嫌疑会落到自己头上),
303
+ // 而拥挤又会把自卫的「退无可退」前提(escapeHopeless 把在场每个人都当障碍)刷成恒真;此档只做任务/回避,杜绝在人堆里误杀。
304
+ if (this.witnessExempt) {
274
305
  this.resetSelfDefense();
275
306
  return null;
276
307
  }
277
- const nearest = this.nearestByDistance(state, suspects);
308
+ const pursuers = threats.filter(p => !this.isHuntTarget(p, ctx));
309
+ if (pursuers.length === 0) {
310
+ this.resetSelfDefense();
311
+ return null;
312
+ }
313
+ const nearest = this.nearestByDistance(state, pursuers);
278
314
  const aimed = this.aimedPursuit(state, nearest);
279
315
 
280
316
  if (!killReady) return null;
@@ -329,6 +365,8 @@ export class WarriorShrimpTop extends Goal {
329
365
  if (juke) {
330
366
  this.selfDefenseJukeWatch = SELF_DEFENSE_JUKE_WINDOW;
331
367
  this.selfDefenseJukeFromPos = { x: state.you.x, y: state.you.y };
368
+ // hBefore:变向前对方朝向,jukeFollowed 拿它和窗口内的当前朝向比【变化量】判断是否响应我变向。
369
+ this.selfDefenseJukePursuerHeading = this.selfDefensePursuerHeading;
332
370
  this.clearSub();
333
371
  return [{ action: Action.move(juke).withThinking('换个方向甩开') }];
334
372
  }
@@ -337,23 +375,37 @@ export class WarriorShrimpTop extends Goal {
337
375
  }
338
376
 
339
377
  /**
340
- * 变向跟随是否成立:站在 suspect 当前位置看,「我发起变向那刻的位置」与「我现在的位置」的夹角已超出 cone
341
- * (半角≈31.8°)。成立才说明我确实横向拉开够远——此时它仍朝我(aimed)= 它跟着我的变向重新瞄准了;
342
- * 直奔我变向前位置/顺着走廊直走的虾此刻必然落在 cone 外、aimed 不成立,不会被算作跟随。
378
+ * 变向跟随是否成立(对方【朝向变化】响应判定,非几何发散):比较追击者在我发起变向前的朝向(hBefore)与变向后
379
+ * 本帧的朝向(hNow),看朝向【变化量】(hNow−hBefore)是否朝我 juke 的方向偏转,且当前朝向确实对准我的新位置:
380
+ * · jukeDir = 我从变向起点到现在的实际位移方向(我往哪侧 juke)。
381
+ * · reaim = (hNow−hBefore)·jukeDir:对方朝向沿我 juke 方向偏转了多少。
382
+ * 同向匀速通勤者朝向恒定(hNow≈hBefore)→ reaim≈0 → 判否,与它是否恰好直行掠过我旧位置无关——这正是旧公式的
383
+ * 漏洞:旧公式只拿【当前朝向】比对【新旧位置】,追击者直行掠过旧位置时「指向旧位置」会翻到它身后,reaim 被刷到
384
+ * ≈2 误判成跟随。真跟随者为追我的横向变向把朝向转向新位置 → (hNow−hBefore) 沿 jukeDir 为正 → 判真。再加 headingAtNew
385
+ * 须 ≥ TOWARD_COS,排除「朝向变了但不是冲我新位置来」。不依赖任何固定转角阈值。
343
386
  */
344
- private jukeFollowed(state: GameState, suspect: PlayerInfo): boolean {
387
+ private jukeFollowed(state: GameState, pursuer: PlayerInfo): boolean {
345
388
  const from = this.selfDefenseJukeFromPos;
346
- if (!from) return false;
347
- const toFrom = unitVec(from.x - suspect.x, from.y - suspect.y);
348
- const toNow = unitVec(state.you.x - suspect.x, state.you.y - suspect.y);
349
- if (!toFrom || !toNow) return false;
350
- return toFrom.x * toNow.x + toFrom.y * toNow.y < SELF_DEFENSE_TOWARD_COS;
389
+ const hBefore = this.selfDefenseJukePursuerHeading;
390
+ const hNow = this.selfDefensePursuerHeading;
391
+ if (!from || !hBefore || !hNow) return false;
392
+ // 我相对变向起点还没真的拉开(位移太小)时,朝向变化无从衡量、jukeDir 也是噪声 先不判,留给窗口里后续帧。
393
+ const jukeDir = unitVec(state.you.x - from.x, state.you.y - from.y);
394
+ if (!jukeDir || dist(from.x, from.y, state.you.x, state.you.y) < SELF_DEFENSE_JUKE_MIN_MOVE) return false;
395
+ const aimNew = unitVec(state.you.x - pursuer.x, state.you.y - pursuer.y);
396
+ if (!aimNew) return false;
397
+ const headingAtNew = hNow.x * aimNew.x + hNow.y * aimNew.y;
398
+ const reaim = (hNow.x - hBefore.x) * jukeDir.x + (hNow.y - hBefore.y) * jukeDir.y;
399
+ return headingAtNew >= SELF_DEFENSE_TOWARD_COS && reaim >= SELF_DEFENSE_REAIM_MARGIN;
351
400
  }
352
401
 
353
402
  /**
354
- * 本帧同一 suspect 是否「朝我贴身追击」:相对上一帧位移 ≥ MOVE_EPS(确实在动,排除站桩做任务)且位移方向
403
+ * 本帧同一追击者是否「朝我贴身追击」:相对上一帧位移 ≥ MOVE_EPS(确实在动,排除站桩做任务)且位移方向
355
404
  * 与「指向我当前位置」夹角余弦 ≥ TOWARD_COS(朝我来;cone 半角≈31.8°)。换目标时整体重置并返回 false。
356
405
  * 只读对方位移与「指向我」的关系,参考点一致;不累计任何转角(转角会被走廊弯曲污染,确认改由变向跟随负责)。
406
+ * 注意:这只是【触发器】,不是敌意判别——直线追我与直线通勤在被试探前逐帧完全相同(朝向都恒定、都指向我),
407
+ * 单凭每帧朝向/方差无法区分;判别力全在 jukeFollowed(试探后对方是否改朝向响应)。顺手把本帧朝向存进
408
+ * selfDefensePursuerHeading 供其使用(仅在 movedFar 时被消费,故站桩噪声朝向进不去)。
357
409
  */
358
410
  private aimedPursuit(state: GameState, nearest: PlayerInfo): boolean {
359
411
  const key = nearest.name || String(nearest.seat ?? '');
@@ -361,21 +413,27 @@ export class WarriorShrimpTop extends Goal {
361
413
  if (key !== this.selfDefenseTargetKey) {
362
414
  this.resetSelfDefense();
363
415
  this.selfDefenseTargetKey = key;
364
- this.selfDefenseSuspectPrev = curS;
416
+ this.selfDefensePursuerPrev = curS;
417
+ return false;
418
+ }
419
+ const sPrev = this.selfDefensePursuerPrev;
420
+ this.selfDefensePursuerPrev = curS;
421
+ if (!sPrev) {
422
+ this.selfDefensePursuerHeading = null;
365
423
  return false;
366
424
  }
367
- const sPrev = this.selfDefenseSuspectPrev;
368
- this.selfDefenseSuspectPrev = curS;
369
- if (!sPrev) return false;
370
425
 
371
426
  const heading = unitVec(curS.x - sPrev.x, curS.y - sPrev.y);
427
+ this.selfDefensePursuerHeading = heading;
372
428
  const aim = unitVec(state.you.x - sPrev.x, state.you.y - sPrev.y);
373
429
  const movedFar = dist(sPrev.x, sPrev.y, curS.x, curS.y) >= SELF_DEFENSE_MOVE_EPS;
374
430
  return !!heading && !!aim && movedFar
375
431
  && heading.x * aim.x + heading.y * aim.y >= SELF_DEFENSE_TOWARD_COS;
376
432
  }
377
433
 
378
- /** planEscape 判定每条逃跑分支都会在推演内被追上(避不开);用与 KeepAwayGoal 一致的基线参数。 */
434
+ /** planEscape 判定每条逃跑分支都会在推演内被追上(避不开);用与 KeepAwayGoal 一致的基线参数。
435
+ * 这是自卫的【必要条件】(退无可退才试探/出刀),不是敌意证据——死路里它对任何人都恒真、与对方是否敌对无关;
436
+ * 敌意判别 100% 来自 jukeFollowed 的朝向响应。 */
379
437
  private escapeHopeless(state: GameState, threats: PlayerInfo[]): boolean {
380
438
  const threatPoints = threats.map(p => ({ x: p.x, y: p.y }));
381
439
  return planEscape(state.you, threatPoints, KEEP_AWAY_BASE_PLAN_OPTS).caught;
@@ -387,14 +445,14 @@ export class WarriorShrimpTop extends Goal {
387
445
  }
388
446
 
389
447
  /**
390
- * 主动变向试探的落点:垂直于「背离 suspect」轴的横向点(略带背离分量以免丢距离),取两侧中离 suspect 更远、
391
- * 且我到落点整段无墙的一侧。两侧都不可走 / 反而明显贴近 suspect(窄道、贴墙)时返回 null,交回常规逃跑。
448
+ * 主动变向试探的落点:垂直于「背离追击者」轴的横向点(略带背离分量以免丢距离),取两侧中离追击者更远、
449
+ * 且我到落点整段无墙的一侧。两侧都不可走 / 反而明显贴近追击者(窄道、贴墙)时返回 null,交回常规逃跑。
392
450
  */
393
- private jukeTarget(state: GameState, suspect: PlayerInfo): Position | null {
451
+ private jukeTarget(state: GameState, pursuer: PlayerInfo): Position | null {
394
452
  const grid = loadWalkableGrid();
395
453
  const me = state.you;
396
- const curSep = dist(me.x, me.y, suspect.x, suspect.y);
397
- const away = unitVec(me.x - suspect.x, me.y - suspect.y) ?? { x: 1, y: 0 };
454
+ const curSep = dist(me.x, me.y, pursuer.x, pursuer.y);
455
+ const away = unitVec(me.x - pursuer.x, me.y - pursuer.y) ?? { x: 1, y: 0 };
398
456
  const sides = [{ x: -away.y, y: away.x }, { x: away.y, y: -away.x }];
399
457
  let best: { target: Position; sep: number } | null = null;
400
458
  for (const perp of sides) {
@@ -409,10 +467,10 @@ export class WarriorShrimpTop extends Goal {
409
467
  const target = grid.cellToWorld(cell);
410
468
  if (dist(me.x, me.y, target.x, target.y) < SELF_DEFENSE_JUKE_MIN_MOVE) continue;
411
469
  if (!segmentWalkable(grid, me.x, me.y, target.x, target.y)) continue;
412
- const sep = dist(target.x, target.y, suspect.x, suspect.y);
470
+ const sep = dist(target.x, target.y, pursuer.x, pursuer.y);
413
471
  if (!best || sep > best.sep) best = { target, sep };
414
472
  }
415
- // 不往 suspect 身上凑:最优侧也比当前明显更近就放弃变向(交回常规逃跑)。
473
+ // 不往追击者身上凑:最优侧也比当前明显更近就放弃变向(交回常规逃跑)。
416
474
  if (best && best.sep < curSep - SELF_DEFENSE_JUKE_MIN_MOVE) return null;
417
475
  return best?.target ?? null;
418
476
  }
@@ -423,6 +481,7 @@ export class WarriorShrimpTop extends Goal {
423
481
  this.selfDefenseStreak = 0;
424
482
  this.selfDefenseJukeWatch = 0;
425
483
  this.selfDefenseJukeFromPos = null;
484
+ this.selfDefenseJukePursuerHeading = null;
426
485
  this.selfDefenseJukeFollows = 0;
427
486
  this.selfDefenseAlerted = false;
428
487
  }
@@ -430,7 +489,8 @@ export class WarriorShrimpTop extends Goal {
430
489
  /** 整体重置:连当前追击目标身份一起丢(换目标、目标消失、开会、转入主动猎杀时用)。 */
431
490
  private resetSelfDefense(): void {
432
491
  this.selfDefenseTargetKey = null;
433
- this.selfDefenseSuspectPrev = null;
492
+ this.selfDefensePursuerPrev = null;
493
+ this.selfDefensePursuerHeading = null;
434
494
  this.softResetPursuit();
435
495
  }
436
496
 
@@ -458,10 +518,10 @@ export class WarriorShrimpTop extends Goal {
458
518
  if (this.subGoal) this.removeSubGoal();
459
519
  this.setSubGoal(new KeepAwayGoal(
460
520
  STRANGER_KEY,
461
- (s, c) => nonTeammatesVisible(s, c).filter(p => this.isUnmarkedStranger(p, c)),
521
+ (s, c) => nonTeammatesVisible(s, c).filter(p => this.isSuspected(p, c)),
462
522
  {
463
523
  threatRadius: STRANGER_RELEASE_RADIUS,
464
- noun: '陌生人',
524
+ noun: '被怀疑者',
465
525
  planOpts: { steps: 4 },
466
526
  } satisfies KeepAwayGoalOptions,
467
527
  ), SUB_PRIORITY);
@@ -484,11 +544,11 @@ export class WarriorShrimpTop extends Goal {
484
544
  if (visible.length === 0) {
485
545
  msg += ',附近无人,做任务/巡逻。';
486
546
  } else if (tier === 'keep-distance') {
487
- msg += ',无标记陌生人靠得太近,先拉开距离再做任务。';
547
+ msg += ',被怀疑者靠得太近,先拉开距离再做任务。';
488
548
  } else {
489
- const bad = visible.filter(p => this.isThreat(p, ctx)).map(p => p.name);
490
- if (bad.length > 0) msg += `,发现认定坏人${bad.join('、')}`;
491
- else if (this.witnessExempt) msg += `,附近有${visible.length}个无标记陌生人互为目击者,正常做任务`;
549
+ const bad = visible.filter(p => this.isHuntTarget(p, ctx)).map(p => p.name);
550
+ if (bad.length > 0) msg += `,发现坏人${bad.join('、')}`;
551
+ else if (this.witnessExempt) msg += `,附近有${visible.length}个被怀疑者互为目击者,正常做任务`;
492
552
  else msg += `,附近有${visible.length}人,正常做任务`;
493
553
  if (tier === 'flee-bad') msg += killCooldownSecs(state) > 0 ? ',刀在冷却,正在回避' : ',正在处理';
494
554
  if (cd > 0) msg += `(攻击冷却${cd}s)`;
@@ -1,20 +1,17 @@
1
1
  # kill-lone 知识契约
2
2
 
3
- `kill-lone` 读取统一的玩家三档标记:
3
+ `kill-lone` 读取两档玩家标记,**未标记的一律默认「被怀疑」**:
4
4
 
5
5
  | 标记 | 行为 |
6
6
  |------|------|
7
- | `suspect` | 不升级为主动猎杀目标,仍按默认落单规则判断 |
8
- | `hostile` | 视为明确猎杀目标,见到就追杀,可突破多人保护规则 |
9
- | `trusted` | 永不击杀,优先级高于其他猎杀判断 |
10
-
11
- 未标记玩家维持默认行为:仅猎杀落单的非队友。
7
+ | `hostile`(坏人) | 视为明确猎杀目标,见到就追杀,可突破多人保护规则 |
8
+ | `trusted`(好人) | 永不击杀,优先级高于其他猎杀判断 |
9
+ | 未标记(被怀疑) | 维持默认行为:仅猎杀落单的非队友,不因被怀疑就升级为主动猎杀 |
12
10
 
13
11
  ```bash
14
- ccl knowledge mark 5 suspect --confidence 0.7 --note "行为可疑"
15
- ccl knowledge mark 5 hostile --confidence 0.9 --note "确认敌对"
12
+ ccl knowledge mark 5 hostile --note "确认敌对"
16
13
  ccl knowledge mark 3 trusted --note "已澄清"
17
- ccl knowledge del player 5
14
+ ccl knowledge del player 5 # 取消标记 → 回到默认「被怀疑」
18
15
  ```
19
16
 
20
17
  `role` 是纯身份事实,不直接控制策略。
@@ -6,7 +6,7 @@ import { SpeechModule, getSpeechConfigForRole } from './speech-module.js';
6
6
 
7
7
  export const strategy: StrategyEntry = {
8
8
  id: 'lone-kill-task',
9
- description: '视野里只有一个非队友且冷却可用就靠近,进入50距离就立刻出刀;刀在冷却时不追不等、改做低优先级任务伪装,冷却好且目标仍落单可见再出刀(贴脸且刀好仍会立刻出刀)。发现尸体且附近有非队友、但当前不能原地出刀时,会靠近并报告尸体。视野里出现两人以上时,暂停攻击10秒并做任意阵营的真实任务伪装;选任务时会跳过紧挨已知尸体(含看见过、已离开视野的)的任务,不在命案现场做任务。没有可做任务时按房间巡逻,绝不停在原地。(章鱼默认)',
9
+ description: '视野里只有一个非队友且冷却可用就靠近,进入50距离就立刻出刀;刀在冷却时不追不等、改做低优先级任务伪装,冷却好且目标仍落单可见再出刀(贴脸且刀好仍会立刻出刀)。发现尸体且附近有非队友、但当前不能原地出刀时,会靠近并报告尸体。视野里出现两人以上时,暂停攻击10秒并做任意阵营的真实任务伪装;选任务时会跳过紧挨已知尸体(含看见过、已离开视野的)的任务,不在命案现场做任务。没有可做任务时按房间巡逻,绝不停在原地。残局(已知存活≤6)出现紧急维修任务时,会高优先级抢着去做(仍低于击杀落单),阻止蟹靠破坏倒计时取胜。(章鱼默认)',
10
10
  create(args?: string[]) {
11
11
  const role = args?.[0] ?? 'neutral_octopus';
12
12
  let convo: ConversationGoal | null = null;
@@ -1,20 +1,19 @@
1
1
  # paradise-fish 知识契约
2
2
 
3
- `paradise-fish` 只读取统一的玩家三档标记:
3
+ `paradise-fish` 只读取两档玩家标记,**未标记的一律默认「被怀疑」**:
4
4
 
5
5
  | 标记 | 行为 |
6
6
  |------|------|
7
- | `suspect` | 可疑:无尸体时进入视野便寻路远离 |
8
- | `hostile` | 敌对:无尸体时进入视野便寻路远离 |
9
- | `trusted` | 可信:可与其抱团、靠近站定伪装做任务 |
7
+ | `hostile`(坏人) | 敌对带刀者:无尸体时进入视野便硬寻路远离 |
8
+ | `trusted`(好人) | 可信:可与其抱团、靠近站定伪装做任务 |
9
+ | 未标记(被怀疑) | 无尸体、无可信同伴且单人贴近时保持距离;多人互为目击者则照常活动 |
10
10
 
11
- 天堂鱼不出刀,因此 suspect 与 hostile 都表现为回避。发现尸体时仍优先执行尸体旁游走逻辑,不报警。
11
+ 天堂鱼不出刀,因此坏人与被怀疑者都表现为回避(坏人硬躲、被怀疑者按目击者规则保持距离)。发现尸体时仍优先执行尸体旁游走逻辑,不报警。
12
12
 
13
13
  ```bash
14
- ccl knowledge mark 5 suspect --confidence 0.7 --note "行为可疑"
15
- ccl knowledge mark 5 hostile --confidence 0.9 --note "确认危险"
14
+ ccl knowledge mark 5 hostile --note "确认危险"
16
15
  ccl knowledge mark 3 trusted --note "可以抱团"
17
- ccl knowledge del player 5
16
+ ccl knowledge del player 5 # 取消标记 → 回到默认「被怀疑」
18
17
  ```
19
18
 
20
19
  `role` 是纯身份事实,不直接控制策略。
@@ -18,7 +18,7 @@ class ParadiseFishStrategy extends GoalRootStrategy {
18
18
  export const strategy: StrategyEntry = {
19
19
  id: 'paradise-fish',
20
20
  description:
21
- '天堂鱼·进阶版(中立,不报警、不做任务,靠被投票出局取胜;corpse-patrol 的带记忆升级)。统一读取 ccl knowledge 三档标记:suspect/hostile 都视为危险并寻路远离,trusted 视为可抱团的可信同伴。发现尸体绝不报警,贴着尸体游走制造嫌疑;附近有人时远离那人但仍在尸体附近活动。传入打招呼话术时,视野内出现人就随机发送一条,之后120秒内不再发言。',
21
+ '天堂鱼·进阶版(中立,不报警、不做任务,靠被投票出局取胜;corpse-patrol 的带记忆升级)。读取 ccl knowledge 两档标记(hostile=坏人 / trusted=好人),未标记的一律默认被怀疑:hostile 视为危险硬寻路远离,被怀疑者按目击者规则保持距离,trusted 视为可抱团的可信同伴。发现尸体绝不报警,贴着尸体游走制造嫌疑;附近有人时远离那人但仍在尸体附近活动。残局(已知存活≤6)出现紧急维修任务时,会在没有带刀坏人需要躲避时优先抢着去做(先于对陌生人保持距离/贴尸/抱团),阻止蟹靠破坏倒计时取胜。传入打招呼话术时,视野内出现人就随机发送一条,之后120秒内不再发言。',
22
22
  create(args?: string[]) {
23
23
  return new ParadiseFishStrategy(parseGreetingArgs(args, 'paradise-fish'));
24
24
  },
@@ -1,20 +1,19 @@
1
1
  # shrimp-memory 知识契约
2
2
 
3
- `shrimp-memory` 只读取统一的玩家三档标记:
3
+ `shrimp-memory` 只读取两档玩家标记,**未标记的一律默认「被怀疑」**:
4
4
 
5
5
  | 标记 | 行为 |
6
6
  |------|------|
7
- | `suspect` | 可疑:进入视野后寻路远离 |
8
- | `hostile` | 敌对:进入视野后寻路远离 |
9
- | `trusted` | 可信:可以结伴做任务,其在场时不对陌生人触发后撤 |
7
+ | `hostile`(坏人) | 敌对:进入视野后硬寻路远离 |
8
+ | `trusted`(好人) | 可信:可以结伴做任务,其在场时不对被怀疑者触发后撤 |
9
+ | 未标记(被怀疑) | trusted 同伴且单人贴近 270px 内时拉开距离,多人互为目击者时照常做任务 |
10
10
 
11
- 普通虾永不出刀,因此 suspect 与 hostile 都表现为回避;两档仍保留,是为了与武士虾等带刀策略共享同一份知识。无标记玩家按陌生人处理:无 trusted 同伴且单人贴近 270px 内时拉开距离,多人互为目击者时照常做任务。
11
+ 普通虾永不出刀,因此坏人硬躲、被怀疑者按目击者规则保持距离。`hostile` 一档与武士虾等带刀策略共享同一份知识(带刀策略刀好时会主动追杀坏人)。
12
12
 
13
13
  ```bash
14
- ccl knowledge mark 5 suspect --confidence 0.7 --note "行为可疑"
15
- ccl knowledge mark 5 hostile --confidence 0.9 --note "确认敌对"
14
+ ccl knowledge mark 5 hostile --note "确认敌对"
16
15
  ccl knowledge mark 3 trusted --note "已澄清"
17
- ccl knowledge del player 5
16
+ ccl knowledge del player 5 # 取消标记 → 回到默认「被怀疑」
18
17
  ```
19
18
 
20
19
  `role` 是纯身份事实,不直接控制策略。
@@ -18,7 +18,7 @@ class ShrimpMemoryStrategy extends GoalRootStrategy {
18
18
  export const strategy: StrategyEntry = {
19
19
  id: 'shrimp-memory',
20
20
  description:
21
- '普通虾·记忆进阶版(好人、不带刀;task-report 的带记忆升级)。统一读取 ccl knowledge 三档标记:suspect/hostile 都寻路远离,trusted 视为可信同伴。发现尸体最优先报警,并提示复核尸体旁身份不明者;无可信同伴且单个陌生人贴近270内时先拉开距离到300再做任务;再处理紧急任务,最后做任务/巡逻(选测地最近、跳过威胁旁或必经威胁的任务)。永不出刀。传入打招呼话术时,视野内出现人就随机发送一条,之后120秒内不再发言。',
21
+ '普通虾·记忆进阶版(好人、不带刀;task-report 的带记忆升级)。读取 ccl knowledge 两档标记(hostile=坏人 / trusted=好人),未标记的一律默认被怀疑:hostile 硬寻路远离,trusted 视为可信同伴,被怀疑者按目击者规则保持距离。发现尸体最优先报警,并提示复核尸体旁身份不明者;无可信同伴且单个被怀疑者贴近270内时先拉开距离到300再做任务;再处理紧急任务,最后做任务/巡逻(选测地最近、跳过威胁旁或必经威胁的任务)。永不出刀。传入打招呼话术时,视野内出现人就随机发送一条,之后120秒内不再发言。',
22
22
  create(args?: string[]) {
23
23
  return new ShrimpMemoryStrategy(parseGreetingArgs(args, 'shrimp-memory'));
24
24
  },
@@ -1,22 +1,21 @@
1
1
  # warrior-memory 知识契约
2
2
 
3
- `warrior-memory` 只读取统一的玩家三档标记:
3
+ `warrior-memory` 只读取两档玩家标记,**未标记的一律默认「被怀疑」**:
4
4
 
5
5
  | 标记 | 行为 |
6
6
  |------|------|
7
- | `suspect` | 可疑:只回避观察,刀可用也绝不出刀(被贴身追击也只逃不反击) |
8
- | `hostile` | 敌对:刀可用时主动追杀;刀冷却或枪虾无次数时回避 |
9
- | `trusted` | 可信:绝不攻击,也不会因其在场而对陌生人后撤 |
7
+ | `hostile`(坏人) | 敌对:刀可用时主动追杀;刀冷却或枪虾无次数时硬回避 |
8
+ | `trusted`(好人) | 可信:绝不攻击,也不会因其在场而对被怀疑者后撤 |
9
+ | 未标记(被怀疑) | 默认回避观察,刀可用也不主动猎杀;仅「绝境自卫」例外(见下) |
10
10
 
11
- **suspect 只回避、不自卫**:对仅标记 suspect 的玩家,武士虾永远只观察、回避、拉开距离,绝不会因为怀疑或被持续贴身追击而先手出刀(被追到死角也只逃)。确认其敌对后请升级标为 hostile,再由刀好时主动追杀。
11
+ **绝境自卫例外(对所有被怀疑者适用)**:当被同一个被怀疑者连续贴身追击、且逃跑推演判定甩不掉时,武士虾会主动横向变一次向试探。只有它跟着变向重新咬住(确认是在追我、不是顺路赶路)、已进 160px 射程、且我到它视线无墙,才会出于自卫先手击杀,并向 agent 推一条「被动自卫」简报(供后续发言/投票口径对齐,别承认无故先手)。直奔身后任务点、顺路掠过的虾不会跟着变向,会被自然甩开、绝不误杀。**不要求先把对方标成坏人**——实战里真凶往往还没被标记就开始追杀,所以对任何被怀疑者都适用。这是最后手段——多数情况下变向只是把追兵甩开,并不出刀。
12
12
 
13
- 无标记玩家按陌生人处理:平时不主动猎杀;无 trusted 同伴且单个陌生人贴近 270px 内时先拉开距离。尸体场景仍有更高优先级:刀可用时会攻击尸体旁最近的非 trusted 非队友,因此确认好人后应及时标为 trusted。
13
+ 被怀疑者(未标记)平时按陌生人处理:不主动猎杀;无 trusted 同伴且单个被怀疑者贴近 270px 内时先拉开距离,多个互为目击者时照常做任务;坏人刀冷时硬躲所有非好人。尸体场景对【未报警的身份不明者】只报警不灭口:站在尸体旁即便身边有被怀疑者,也绝不先手击杀,一律靠近报警;但若尸体旁是 hostile 或启动猎杀目标、刀可用,仍会现场先手击杀(凶手就在身边照杀)。
14
14
 
15
15
  ```bash
16
- ccl knowledge mark 5 suspect --confidence 0.7 --note "电力房尾随"
17
- ccl knowledge mark 5 hostile --confidence 0.9 --note "确认敌对"
16
+ ccl knowledge mark 5 hostile --note "确认敌对"
18
17
  ccl knowledge mark 3 trusted --note "不在场证明成立"
19
- ccl knowledge del player 5
18
+ ccl knowledge del player 5 # 取消标记 → 回到默认「被怀疑」
20
19
  ```
21
20
 
22
- 启动参数 `ccl strategy warrior-memory -- 3 7` 仍表示本次策略运行的明确猎杀目标。`role` 是纯身份事实,不直接控制策略。
21
+ 启动参数 `ccl strategy warrior-memory -- 3 7` 仍表示本次策略运行的明确猎杀目标(等同坏人)。`role` 是纯身份事实,不直接控制策略。
@@ -6,7 +6,7 @@ import { parseTargetArgs } from './player-targets.js';
6
6
  export const strategy: StrategyEntry = {
7
7
  id: 'warrior-memory',
8
8
  description:
9
- '带刀虾·记忆进阶版(武士虾/枪虾通用,好人带刀;kill-lone 的带记忆升级)。统一读取 ccl knowledge 三档标记:suspect 只回避观察、刀可用也绝不出刀(被贴身追击也只逃不反击,确认敌对请升级为 hostile),hostile 在刀好时主动追杀、刀不好时回避,trusted 视为可信同伴且绝不攻击。发现尸体且旁边有非 trusted 对象、刀可用就先出刀,否则靠近报警;无可信同伴且单个陌生人贴近270内时先拉开到300再做任务;无危险时优先紧急任务,否则做任务/巡逻。枪虾会尊重剩余出刀次数。',
9
+ '带刀虾·记忆进阶版(武士虾/枪虾通用,好人带刀;kill-lone 的带记忆升级)。读取 ccl knowledge 两档标记(hostile=坏人 / trusted=好人),未标记的一律默认被怀疑:hostile 刀好时主动追杀、刀不好时硬回避,trusted 视为可信同伴且绝不攻击,被怀疑者默认只回避观察——仅当被同一个被怀疑者持续贴身追击、避让判定甩不掉时主动横向变向试探,只有它跟着变向仍咬住、进了射程且视线无墙才自卫先手击杀(被动自卫,会推 agent 简报;直奔任务点顺路掠过的虾不跟变向、自然甩开不误杀;不要求先标记,实战里真凶常没来得及标就追杀)。发现尸体优先报警;尸体旁若是 hostile/启动猎杀目标且刀可用会现场先手击杀,但绝不会因尸体旁有未报警的身份不明者就灭口(已根治该误杀);无可信同伴且单个被怀疑者贴近270内时先拉开到300再做任务;无危险时优先紧急任务,否则做任务/巡逻。枪虾会尊重剩余出刀次数。',
10
10
  create(args?: string[]) {
11
11
  const huntTargets = args ? parseTargetArgs(args) : [];
12
12
  return new GoalRootStrategy('warrior-memory', () => new WarriorShrimpTop(huntTargets), {