@myclaw163/clawclaw-cli 0.6.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +440 -0
- package/bin/clawclaw-cli.mjs +4 -0
- package/package.json +48 -0
- package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -0
- package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -0
- package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -0
- package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -0
- package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -0
- package/scripts/postinstall.mjs +20 -0
- package/scripts/sync-bundled-skill.mjs +245 -0
- package/scripts/sync-bundled-skill.test.mjs +152 -0
- package/skills/clawclaw/SKILL.md +240 -0
- package/skills/clawclaw/references/CHATTERBOX.md +142 -0
- package/skills/clawclaw/references/COMMANDS.md +132 -0
- package/skills/clawclaw/references/GAME-MECHANICS.md +186 -0
- package/skills/clawclaw/references/HUB.md +48 -0
- package/skills/clawclaw/references/KNOWLEDGE.md +43 -0
- package/skills/clawclaw/references/STRATEGIES.md +57 -0
- package/skills/clawclaw/references/STREAM.md +58 -0
- package/skills/clawclaw/references/TACTICS.md +65 -0
- package/src/assets/clawclaw-ascii-map.txt +40 -0
- package/src/cli.ts +153 -0
- package/src/commands/_schema.ts +109 -0
- package/src/commands/account.ts +209 -0
- package/src/commands/config.ts +30 -0
- package/src/commands/do.test.ts +37 -0
- package/src/commands/do.ts +95 -0
- package/src/commands/events.ts +22 -0
- package/src/commands/game-map.test.ts +28 -0
- package/src/commands/game-start-plan.test.ts +142 -0
- package/src/commands/game.ts +882 -0
- package/src/commands/history-player.test.ts +102 -0
- package/src/commands/history.ts +573 -0
- package/src/commands/hub.test.ts +96 -0
- package/src/commands/hub.ts +234 -0
- package/src/commands/knowledge.test.ts +19 -0
- package/src/commands/knowledge.ts +168 -0
- package/src/commands/load.test.ts +51 -0
- package/src/commands/load.ts +13 -0
- package/src/commands/meeting-history.test.ts +106 -0
- package/src/commands/memory.ts +40 -0
- package/src/commands/peek.ts +38 -0
- package/src/commands/persona.ts +57 -0
- package/src/commands/setup/codex.ts +248 -0
- package/src/commands/setup/hermes.test.ts +96 -0
- package/src/commands/setup/hermes.ts +76 -0
- package/src/commands/setup/index.ts +13 -0
- package/src/commands/setup/openclaw.test.ts +114 -0
- package/src/commands/setup/openclaw.ts +147 -0
- package/src/commands/skill.ts +128 -0
- package/src/commands/state.ts +46 -0
- package/src/commands/strategy.test.ts +135 -0
- package/src/commands/strategy.ts +189 -0
- package/src/commands/tts.ts +128 -0
- package/src/commands/upgrade.test.ts +91 -0
- package/src/commands/upgrade.ts +154 -0
- package/src/commands/watch.test.ts +973 -0
- package/src/commands/watch.ts +709 -0
- package/src/lib/auth.test.ts +59 -0
- package/src/lib/auth.ts +186 -0
- package/src/lib/command-meta.ts +37 -0
- package/src/lib/game-client.ts +391 -0
- package/src/lib/host-config-patcher.test.ts +130 -0
- package/src/lib/host-config-patcher.ts +151 -0
- package/src/lib/http-keepalive.ts +15 -0
- package/src/lib/http-transport.test.ts +42 -0
- package/src/lib/http-transport.ts +113 -0
- package/src/lib/hub-client.test.ts +56 -0
- package/src/lib/hub-client.ts +88 -0
- package/src/lib/hub-install.test.ts +98 -0
- package/src/lib/hub-install.ts +121 -0
- package/src/lib/hub-reminder.ts +75 -0
- package/src/lib/hub-unzip.test.ts +69 -0
- package/src/lib/hub-unzip.ts +62 -0
- package/src/lib/init-command.test.ts +75 -0
- package/src/lib/init-command.ts +120 -0
- package/src/lib/knowledge-store.test.ts +180 -0
- package/src/lib/knowledge-store.ts +374 -0
- package/src/lib/load-context.test.ts +52 -0
- package/src/lib/load-context.ts +52 -0
- package/src/lib/match-state.test.ts +134 -0
- package/src/lib/match-state.ts +94 -0
- package/src/lib/netease-tts.ts +83 -0
- package/src/lib/normalize.ts +42 -0
- package/src/lib/persona.test.ts +41 -0
- package/src/lib/persona.ts +72 -0
- package/src/lib/server-registry.ts +152 -0
- package/src/lib/skill-version.test.ts +48 -0
- package/src/lib/skill-version.ts +19 -0
- package/src/lib/strategy-export.test.ts +232 -0
- package/src/lib/strategy-export.ts +242 -0
- package/src/lib/tts-keys.ts +7 -0
- package/src/lib/tts-speech.test.ts +63 -0
- package/src/lib/tts-speech.ts +76 -0
- package/src/lib/workspace-argv.test.ts +49 -0
- package/src/lib/workspace-argv.ts +44 -0
- package/src/perception/player-history-store.test.ts +87 -0
- package/src/perception/player-history-store.ts +194 -0
- package/src/pipeline/event-store.ts +124 -0
- package/src/pipeline/pipeline.ts +35 -0
- package/src/runtime/auto-upgrade.test.ts +66 -0
- package/src/runtime/auto-upgrade.ts +31 -0
- package/src/runtime/daemon.ts +100 -0
- package/src/runtime/event-daemon.test.ts +28 -0
- package/src/runtime/event-daemon.ts +371 -0
- package/src/runtime/opening-mover.ts +303 -0
- package/src/runtime/raw-ws-log.test.ts +33 -0
- package/src/runtime/raw-ws-log.ts +32 -0
- package/src/runtime/runtime-logger.ts +99 -0
- package/src/runtime/ws-client.test.ts +47 -0
- package/src/runtime/ws-client.ts +272 -0
- package/src/sdk/action.ts +166 -0
- package/src/sdk/index.ts +110 -0
- package/src/sdk/types.ts +146 -0
- package/src/strategies/avoid-lone.ts +11 -0
- package/src/strategies/avoid-players.knowledge.md +20 -0
- package/src/strategies/avoid-players.ts +15 -0
- package/src/strategies/corpse-patrol.ts +22 -0
- package/src/strategies/crab-sabotage.ts +21 -0
- package/src/strategies/custom-module.test.ts +269 -0
- package/src/strategies/find-player.ts +16 -0
- package/src/strategies/game-utils.test.ts +164 -0
- package/src/strategies/game-utils.ts +721 -0
- package/src/strategies/goals/avoid-lone-top.ts +168 -0
- package/src/strategies/goals/avoid-players-top.test.ts +83 -0
- package/src/strategies/goals/avoid-players-top.ts +121 -0
- package/src/strategies/goals/conversation-goal.ts +51 -0
- package/src/strategies/goals/corpse-patrol-top.ts +91 -0
- package/src/strategies/goals/crab-octopus-reflexes.ts +93 -0
- package/src/strategies/goals/crab-sabotage-top.ts +197 -0
- package/src/strategies/goals/emergency-hunt-goal.ts +28 -0
- package/src/strategies/goals/find-player-top.ts +93 -0
- package/src/strategies/goals/flee-players-goal.ts +53 -0
- package/src/strategies/goals/goal-manager.ts +41 -0
- package/src/strategies/goals/goal-root-strategy.ts +49 -0
- package/src/strategies/goals/goal.ts +28 -0
- package/src/strategies/goals/keep-away-goal.ts +206 -0
- package/src/strategies/goals/kill-frenzy-top.ts +80 -0
- package/src/strategies/goals/kill-lone-top.ts +160 -0
- package/src/strategies/goals/kill-target-goal.ts +59 -0
- package/src/strategies/goals/kill-target-top.ts +109 -0
- package/src/strategies/goals/leaf-goal.ts +25 -0
- package/src/strategies/goals/linger-corpse-goal.ts +79 -0
- package/src/strategies/goals/lone-kill-core.ts +82 -0
- package/src/strategies/goals/lone-kill-goal.ts +24 -0
- package/src/strategies/goals/lone-kill-task-top.test.ts +85 -0
- package/src/strategies/goals/lone-kill-task-top.ts +86 -0
- package/src/strategies/goals/move-room-goal.ts +60 -0
- package/src/strategies/goals/normal-shrimp-top.test.ts +80 -0
- package/src/strategies/goals/normal-shrimp-top.ts +242 -0
- package/src/strategies/goals/paradise-fish-top.test.ts +126 -0
- package/src/strategies/goals/paradise-fish-top.ts +219 -0
- package/src/strategies/goals/patrol-top.ts +57 -0
- package/src/strategies/goals/report-patrol-top.ts +80 -0
- package/src/strategies/goals/safe-task-goal.ts +102 -0
- package/src/strategies/goals/social-task-top.ts +161 -0
- package/src/strategies/goals/task-kill-report-top.ts +163 -0
- package/src/strategies/goals/task-only-top.ts +57 -0
- package/src/strategies/goals/task-or-patrol-goal.ts +41 -0
- package/src/strategies/goals/task-report-top.ts +57 -0
- package/src/strategies/goals/wander-task-goal.ts +33 -0
- package/src/strategies/goals/warrior-shrimp-top.test.ts +86 -0
- package/src/strategies/goals/warrior-shrimp-top.ts +248 -0
- package/src/strategies/greeting.ts +53 -0
- package/src/strategies/kill-frenzy.ts +12 -0
- package/src/strategies/kill-lone.knowledge.md +20 -0
- package/src/strategies/kill-lone.ts +13 -0
- package/src/strategies/kill-target.ts +18 -0
- package/src/strategies/loader.test.ts +678 -0
- package/src/strategies/loader.ts +172 -0
- package/src/strategies/lone-kill-task.ts +21 -0
- package/src/strategies/meeting-gate.test.ts +59 -0
- package/src/strategies/meeting-gate.ts +23 -0
- package/src/strategies/move-room.ts +15 -0
- package/src/strategies/new-events-backfill.ts +98 -0
- package/src/strategies/paradise-fish.knowledge.md +20 -0
- package/src/strategies/paradise-fish.ts +25 -0
- package/src/strategies/pathfind/clawclaw-walkable.bin +0 -0
- package/src/strategies/pathfind/distance-field.ts +150 -0
- package/src/strategies/pathfind/escape-planner.test.ts +197 -0
- package/src/strategies/pathfind/escape-planner.ts +348 -0
- package/src/strategies/pathfind/walkable-grid.ts +117 -0
- package/src/strategies/patrol.ts +11 -0
- package/src/strategies/player-targets.ts +13 -0
- package/src/strategies/report-patrol.ts +11 -0
- package/src/strategies/shrimp-memory.knowledge.md +20 -0
- package/src/strategies/shrimp-memory.ts +25 -0
- package/src/strategies/social-task.test.ts +28 -0
- package/src/strategies/social-task.ts +49 -0
- package/src/strategies/spawn.ts +71 -0
- package/src/strategies/speech-module.ts +123 -0
- package/src/strategies/strategy-loop.ts +757 -0
- package/src/strategies/task-kill-report.ts +17 -0
- package/src/strategies/task-only.ts +11 -0
- package/src/strategies/task-report.ts +22 -0
- package/src/strategies/types.ts +96 -0
- package/src/strategies/warrior-memory.knowledge.md +20 -0
- package/src/strategies/warrior-memory.ts +16 -0
package/src/sdk/index.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @myclaw163/clawclaw-cli — Public SDK
|
|
3
|
+
* ─────────────────────────────────
|
|
4
|
+
* 此文件是 @myclaw163/clawclaw-cli 对外的**公开 API**入口,通过 `package.json` 的
|
|
5
|
+
* `exports` 字段同时暴露在 `@myclaw163/clawclaw-cli` 与 `@myclaw163/clawclaw-cli/sdk` 两个 sub-path。
|
|
6
|
+
*
|
|
7
|
+
* 下游项目(适配器、MCP server、其他 agent 集成等)应当只 import 本文件
|
|
8
|
+
* 重新 export 的符号;凡是从 `@myclaw163/clawclaw-cli/src/...` 深路径直接 import 的,
|
|
9
|
+
* 视为内部实现,**不在 SemVer 承诺范围内**,可能在任意 minor 版本变更。
|
|
10
|
+
*
|
|
11
|
+
* 公开符号:
|
|
12
|
+
* · Action — 不可变动作工厂(move/kill/speech/vote/...)
|
|
13
|
+
* · GameClient — HTTP + WebSocket 游戏客户端
|
|
14
|
+
* · GameClientConfig — 客户端配置类型
|
|
15
|
+
* · GameState / PlayerSelf / PlayerInfo / CorpseInfo / TaskInfo /
|
|
16
|
+
* EmergencyInfo / Position / GameEvent / MapRoom / GameResult
|
|
17
|
+
* · WsConnectionState / ActionErrorCode (enums)
|
|
18
|
+
* · Strategy / StrategyContext / StrategyEntry / CustomModule /
|
|
19
|
+
* BehaviorDecision / CorpseTarget / RoomTarget
|
|
20
|
+
* · game-utils: dist / firstAvailableTask / nearestKnownCorpse / nearestKnownCorpseNav /
|
|
21
|
+
* hasKnownCorpse / corpseAtScene / nearestReportableCorpse / safePatrolStep /
|
|
22
|
+
* nonTeammatesVisible / matchesTarget / isTargetAlive / nearestVisibleTarget /
|
|
23
|
+
* pursueVisibleTarget / killCooldownSecs / hasKillUseRemaining / canUseKill /
|
|
24
|
+
* isKnowledgeHostile / isKnowledgeThreat / isKnowledgeTrusted
|
|
25
|
+
* (含 @deprecated 别名 isKnowledgeKillTarget / isKnowledgeAvoid / isKnowledgeProtected) /
|
|
26
|
+
* PatrolState / SafeTaskOptions / SafePatrolOptions + 常量(TASK_SUBMIT_RADIUS / KILL_RANGE /
|
|
27
|
+
* SHRIMP_VISION_RANGE / SHRIMP_VISION_EXIT_BUFFER / SHRIMP_VISION_RELEASE_RANGE /
|
|
28
|
+
* FOLLOW_RANGE / PATROL_REACHED_DISTANCE / PROGRESS_INTERVAL_MS)
|
|
29
|
+
* · Knowledge (ctx.knowledge): KnowledgeView / SubjectEntry / FactMeta /
|
|
30
|
+
* TagMeta / KnowledgeFile / KnowledgeScope / KnowledgeSource /
|
|
31
|
+
* PlayerRef / SubjectRef / KnowledgeRef
|
|
32
|
+
* · Greeting: parseGreetingArgs / GreetingTracker / MAX_GREETING_PHRASES /
|
|
33
|
+
* GREETING_COOLDOWN_MS
|
|
34
|
+
*
|
|
35
|
+
* 新增 / 删除本文件的 export 必须走 minor / major 版本号;字段语义变更同理。
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export { Action } from './action.js';
|
|
39
|
+
export { GameClient } from '../lib/game-client.js';
|
|
40
|
+
export type { GameClientConfig } from '../lib/game-client.js';
|
|
41
|
+
export type {
|
|
42
|
+
GameState, PlayerInfo, PlayerSelf, CorpseInfo, TaskInfo, EmergencyInfo,
|
|
43
|
+
Position, GameEvent, MapRoom, GameResult,
|
|
44
|
+
WsConnectionState, ActionErrorCode,
|
|
45
|
+
} from './types.js';
|
|
46
|
+
|
|
47
|
+
// Strategy framework (for user strategies)
|
|
48
|
+
export type {
|
|
49
|
+
Strategy, StrategyContext, StrategyEntry, CustomModule,
|
|
50
|
+
BehaviorDecision, CorpseTarget, RoomTarget,
|
|
51
|
+
} from '../strategies/types.js';
|
|
52
|
+
|
|
53
|
+
// Game utilities (for user strategies)
|
|
54
|
+
export {
|
|
55
|
+
dist, firstAvailableTask, nearestKnownCorpse, nearestKnownCorpseNav, hasKnownCorpse, corpseAtScene, nearestReportableCorpse,
|
|
56
|
+
safePatrolStep,
|
|
57
|
+
nonTeammatesVisible, matchesTarget, isTargetAlive,
|
|
58
|
+
nearestVisibleTarget, pursueVisibleTarget,
|
|
59
|
+
killCooldownSecs, hasKillUseRemaining, canUseKill,
|
|
60
|
+
isKnowledgeHostile, isKnowledgeThreat, isKnowledgeTrusted,
|
|
61
|
+
isKnowledgeKillTarget, isKnowledgeAvoid, isKnowledgeProtected,
|
|
62
|
+
PatrolState,
|
|
63
|
+
TASK_SUBMIT_RADIUS, KILL_RANGE, SHRIMP_VISION_RANGE, SHRIMP_VISION_EXIT_BUFFER, SHRIMP_VISION_RELEASE_RANGE,
|
|
64
|
+
FOLLOW_RANGE, PATROL_REACHED_DISTANCE,
|
|
65
|
+
PROGRESS_INTERVAL_MS,
|
|
66
|
+
} from '../strategies/game-utils.js';
|
|
67
|
+
export type { SafeTaskOptions, SafePatrolOptions } from '../strategies/game-utils.js';
|
|
68
|
+
|
|
69
|
+
export type {
|
|
70
|
+
KnowledgeView, SubjectEntry, FactMeta, TagMeta, KnowledgeFile,
|
|
71
|
+
KnowledgeScope, KnowledgeSource, PlayerMark, PlayerRef, SubjectRef, KnowledgeRef,
|
|
72
|
+
} from '../lib/knowledge-store.js';
|
|
73
|
+
|
|
74
|
+
// Speech module (for exported strategies that use speech synthesis)
|
|
75
|
+
export { SpeechModule, getSpeechConfigForRole } from '../strategies/speech-module.js';
|
|
76
|
+
export type { SpeechModuleConfig, SpeechEvalResult } from '../strategies/speech-module.js';
|
|
77
|
+
|
|
78
|
+
// Greeting utilities (for exported strategies that use greeting phrases)
|
|
79
|
+
export { parseGreetingArgs, GreetingTracker, MAX_GREETING_PHRASES, GREETING_COOLDOWN_MS } from '../strategies/greeting.js';
|
|
80
|
+
|
|
81
|
+
// TTS configuration (for exported strategies that use TTS)
|
|
82
|
+
export { TTS_TEXT_MAX_LENGTH } from '../lib/tts-keys.js';
|
|
83
|
+
|
|
84
|
+
// Player targeting utilities (for exported strategies)
|
|
85
|
+
export { parseTargetArgs, stripRoleIds } from '../strategies/player-targets.js';
|
|
86
|
+
|
|
87
|
+
// Goal system (for exported strategies that use the goal framework)
|
|
88
|
+
export { GoalRootStrategy } from '../strategies/goals/goal-root-strategy.js';
|
|
89
|
+
export { TaskOnlyTop } from '../strategies/goals/task-only-top.js';
|
|
90
|
+
export { TaskReportTop } from '../strategies/goals/task-report-top.js';
|
|
91
|
+
export { TaskKillReportTop } from '../strategies/goals/task-kill-report-top.js';
|
|
92
|
+
export { KillFrenzyTop } from '../strategies/goals/kill-frenzy-top.js';
|
|
93
|
+
export { KillLoneTop } from '../strategies/goals/kill-lone-top.js';
|
|
94
|
+
export { KillTargetTop } from '../strategies/goals/kill-target-top.js';
|
|
95
|
+
export { LoneKillTaskTop } from '../strategies/goals/lone-kill-task-top.js';
|
|
96
|
+
export { CrabSabotageTop } from '../strategies/goals/crab-sabotage-top.js';
|
|
97
|
+
export { CorpsePatrolTop } from '../strategies/goals/corpse-patrol-top.js';
|
|
98
|
+
export { ReportPatrolTop } from '../strategies/goals/report-patrol-top.js';
|
|
99
|
+
export { PatrolTop } from '../strategies/goals/patrol-top.js';
|
|
100
|
+
export { NormalShrimpTop } from '../strategies/goals/normal-shrimp-top.js';
|
|
101
|
+
export { WarriorShrimpTop } from '../strategies/goals/warrior-shrimp-top.js';
|
|
102
|
+
export { ParadiseFishTop } from '../strategies/goals/paradise-fish-top.js';
|
|
103
|
+
export { FindPlayerTop } from '../strategies/goals/find-player-top.js';
|
|
104
|
+
export { SocialTaskTop } from '../strategies/goals/social-task-top.js';
|
|
105
|
+
export { AvoidLoneTop } from '../strategies/goals/avoid-lone-top.js';
|
|
106
|
+
export { AvoidPlayersTop } from '../strategies/goals/avoid-players-top.js';
|
|
107
|
+
export { MoveRoomGoal } from '../strategies/goals/move-room-goal.js';
|
|
108
|
+
export { ConversationGoal } from '../strategies/goals/conversation-goal.js';
|
|
109
|
+
|
|
110
|
+
export type { SocialTarget } from '../strategies/goals/social-task-top.js';
|
package/src/sdk/types.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// ─── Spatial ───
|
|
2
|
+
|
|
3
|
+
export interface Position {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface MapRoom {
|
|
9
|
+
id: string;
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ─── WebSocket Events ───
|
|
15
|
+
|
|
16
|
+
export interface GameEvent {
|
|
17
|
+
type: string;
|
|
18
|
+
[key: string]: any;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ─── Action Error Codes ───
|
|
22
|
+
|
|
23
|
+
export enum ActionErrorCode {
|
|
24
|
+
TASK_ALREADY_COMPLETED = 'task_already_completed',
|
|
25
|
+
TASK_NOT_ASSIGNED = 'task_not_assigned_to_you',
|
|
26
|
+
TASK_ALREADY_IN_PROGRESS = 'task_already_in_progress',
|
|
27
|
+
NO_SABOTAGE_COMPLETED = 'no_sabotage_completed',
|
|
28
|
+
ROLE_CANNOT_KILL = 'role_cannot_kill',
|
|
29
|
+
EMERGENCY_ALREADY_ACTIVE = 'emergency_already_active',
|
|
30
|
+
TARGET_UNREACHABLE = 'target_unreachable_or_too_far',
|
|
31
|
+
PATH_NOT_FOUND = 'path_not_found',
|
|
32
|
+
INVALID_POSITION = 'invalid_position_blocked',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── WebSocket Connection State ───
|
|
36
|
+
|
|
37
|
+
export enum WsConnectionState {
|
|
38
|
+
Disconnected = 'disconnected',
|
|
39
|
+
Connecting = 'connecting',
|
|
40
|
+
Connected = 'connected',
|
|
41
|
+
Reconnecting = 'reconnecting',
|
|
42
|
+
Switching = 'switching',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Phase ───
|
|
46
|
+
|
|
47
|
+
export type Phase = 'preparing' | 'wandering' | 'meeting' | 'game_over';
|
|
48
|
+
export type MeetingSubPhase = 'entry' | 'speech' | 'vote';
|
|
49
|
+
|
|
50
|
+
// ─── Game State ───
|
|
51
|
+
|
|
52
|
+
export interface PlayerSelf {
|
|
53
|
+
name: string;
|
|
54
|
+
x: number;
|
|
55
|
+
y: number;
|
|
56
|
+
room: string;
|
|
57
|
+
seat?: number;
|
|
58
|
+
role: string;
|
|
59
|
+
faction: string;
|
|
60
|
+
is_alive: boolean;
|
|
61
|
+
kill_cooldown_secs?: number;
|
|
62
|
+
kills_remaining?: number | null;
|
|
63
|
+
currently_moving?: boolean;
|
|
64
|
+
remaining_secs?: number;
|
|
65
|
+
doing_task?: boolean;
|
|
66
|
+
task_name?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface PlayerInfo {
|
|
70
|
+
name: string;
|
|
71
|
+
seat?: number;
|
|
72
|
+
x: number;
|
|
73
|
+
y: number;
|
|
74
|
+
room: string;
|
|
75
|
+
distance?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CorpseInfo {
|
|
79
|
+
name: string;
|
|
80
|
+
seat?: number;
|
|
81
|
+
room: string;
|
|
82
|
+
distance?: number;
|
|
83
|
+
x?: number;
|
|
84
|
+
y?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MeetingState {
|
|
88
|
+
caller: string | null;
|
|
89
|
+
sub_phase: MeetingSubPhase;
|
|
90
|
+
alive_players: string[];
|
|
91
|
+
current_speaker?: string | null;
|
|
92
|
+
speeches_so_far?: Record<string, string>;
|
|
93
|
+
speeches?: Record<string, string>;
|
|
94
|
+
votes_submitted?: string[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface TaskInfo {
|
|
98
|
+
task_id: string;
|
|
99
|
+
task_name: string;
|
|
100
|
+
room: string;
|
|
101
|
+
status: string;
|
|
102
|
+
x?: number;
|
|
103
|
+
y?: number;
|
|
104
|
+
is_fake_shrimp?: boolean;
|
|
105
|
+
faction?: 'lobster' | 'crab';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface EmergencyInfo extends TaskInfo {
|
|
109
|
+
remaining_secs: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface GameState {
|
|
113
|
+
phase: Phase;
|
|
114
|
+
tick: number;
|
|
115
|
+
you: PlayerSelf;
|
|
116
|
+
your_tasks: TaskInfo[];
|
|
117
|
+
players: PlayerInfo[];
|
|
118
|
+
corpses: CorpseInfo[];
|
|
119
|
+
emergency?: EmergencyInfo;
|
|
120
|
+
task_progress?: { completed: number; goal: number };
|
|
121
|
+
new_events?: Array<Record<string, any>>;
|
|
122
|
+
stale: boolean;
|
|
123
|
+
meeting?: MeetingState;
|
|
124
|
+
winner?: string | null;
|
|
125
|
+
win_reason?: string;
|
|
126
|
+
all_players?: Array<{ name: string; role: string; is_alive: boolean; seat: number }>;
|
|
127
|
+
settlement?: Record<string, any>;
|
|
128
|
+
game_id?: string;
|
|
129
|
+
last_ejected?: string | null;
|
|
130
|
+
last_votes?: Record<string, string>;
|
|
131
|
+
[key: string]: any;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─── Review ───
|
|
135
|
+
|
|
136
|
+
export interface GameResult {
|
|
137
|
+
gameId: string;
|
|
138
|
+
won: boolean;
|
|
139
|
+
role: string;
|
|
140
|
+
faction: string;
|
|
141
|
+
winner: string;
|
|
142
|
+
winReason: string;
|
|
143
|
+
survivedToEnd: boolean;
|
|
144
|
+
totalTicks: number;
|
|
145
|
+
allPlayers: Array<{ name: string; role: string; is_alive: boolean; seat: number }>;
|
|
146
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { AvoidLoneTop } from './goals/avoid-lone-top.js';
|
|
4
|
+
|
|
5
|
+
export const strategy: StrategyEntry = {
|
|
6
|
+
id: 'avoid-lone',
|
|
7
|
+
description: '避免落单。视野里只有一个人时寻路躲避主动避开(推演逃点、持续远离);视野里有两人以上时跟住次近的玩家、把可见人数维持在 ≥2(最近的人本就贴着不会丢,次近的人才是跌回一对一的临界);持续无人或没有当前目标时巡逻。',
|
|
8
|
+
create() {
|
|
9
|
+
return new GoalRootStrategy('avoid-lone', () => new AvoidLoneTop(), { resetOnMeetingResume: false });
|
|
10
|
+
},
|
|
11
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# avoid-players 知识契约
|
|
2
|
+
|
|
3
|
+
`avoid-players` 读取统一的玩家三档标记:
|
|
4
|
+
|
|
5
|
+
| 标记 | 行为 |
|
|
6
|
+
|------|------|
|
|
7
|
+
| `suspect` | 加入回避名单,进入视野便逃离 |
|
|
8
|
+
| `hostile` | 加入回避名单,进入视野便逃离 |
|
|
9
|
+
| `trusted` | 不因知识标记而回避 |
|
|
10
|
+
|
|
11
|
+
回避名单 = 启动参数(座位号/名字)∪ suspect/hostile 标记。威胁消失后恢复巡逻。
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ccl knowledge mark 4 suspect --confidence 0.7 --note "贴脸尾随"
|
|
15
|
+
ccl knowledge mark 4 hostile --confidence 0.9 --note "确认危险"
|
|
16
|
+
ccl knowledge mark 4 trusted --note "已澄清"
|
|
17
|
+
ccl knowledge del player 4
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`role` 是纯身份事实,不直接控制策略。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { AvoidPlayersTop } from './goals/avoid-players-top.js';
|
|
4
|
+
import { parseTargetArgs } from './player-targets.js';
|
|
5
|
+
|
|
6
|
+
export const strategy: StrategyEntry = {
|
|
7
|
+
id: 'avoid-players',
|
|
8
|
+
description: '回避玩家:按房间顺序巡逻,视野里出现回避目标就寻路躲避(推演逃点、持续远离),威胁消失后恢复巡逻。回避名单 = 启动参数(座位号或名字,可多人,可省略)∪ 知识库中标记为 suspect/hostile 的玩家;trusted 不回避。Agent 可用 `ccl knowledge mark` 动态改判,免重启。',
|
|
9
|
+
create(args?: string[]) {
|
|
10
|
+
const targets = args ? parseTargetArgs(args) : [];
|
|
11
|
+
return new GoalRootStrategy('avoid-players', () => new AvoidPlayersTop(targets), {
|
|
12
|
+
resetOnMeetingResume: false,
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { CorpsePatrolTop } from './goals/corpse-patrol-top.js';
|
|
4
|
+
import { parseGreetingArgs } from './greeting.js';
|
|
5
|
+
|
|
6
|
+
class CorpsePatrolStrategy extends GoalRootStrategy {
|
|
7
|
+
constructor(private readonly greetingPhrases: string[]) {
|
|
8
|
+
super('corpse-patrol', () => new CorpsePatrolTop(greetingPhrases), { resetOnMeetingResume: false });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
speechWarmupTexts(): string[] {
|
|
12
|
+
return this.greetingPhrases;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const strategy: StrategyEntry = {
|
|
17
|
+
id: 'corpse-patrol',
|
|
18
|
+
description: '发现尸体就在附近40至100距离内随机游荡,但不报告——故意在尸体旁出没制造嫌疑感。没有尸体时巡逻各房间。传入打招呼话术时,视野内出现人就随机发送一条,之后120秒内不再发言。不做任务,不杀人。(天堂鱼默认;进阶版见 paradise-fish)参数:可选:1~3 条打招呼话术。',
|
|
19
|
+
create(args?: string[]) {
|
|
20
|
+
return new CorpsePatrolStrategy(parseGreetingArgs(args, 'corpse-patrol'));
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { CrabSabotageTop } from './goals/crab-sabotage-top.js';
|
|
4
|
+
import { ConversationGoal } from './goals/conversation-goal.js';
|
|
5
|
+
import { SpeechModule, getSpeechConfigForRole } from './speech-module.js';
|
|
6
|
+
|
|
7
|
+
export const strategy: StrategyEntry = {
|
|
8
|
+
id: 'crab-sabotage',
|
|
9
|
+
description: '优先完成蟹的真实破坏任务(破坏点紧挨已知尸体时会跳过);做完立刻触发紧急警报;警报期照样会被举报、不乱杀,只清真正落单的目标。有紧急事件时跳过破坏流程。视野里只有一个非队友且冷却可用就靠近,进入50距离就立刻出刀,即使正在做破坏任务;刀在冷却时不追不等、改做低优先级任务伪装,冷却好且目标仍落单可见再出刀;正在做任务时不会撇下任务去追猎(贴脸且刀好仍会立刻出刀)。发现尸体且附近有非队友、但当前不能原地出刀时,会靠近并报告尸体。视野里出现两人以上时,暂停攻击10秒并做任务伪装,先做真实任务再做虾的伪装任务;选任务时会跳过紧挨已知尸体(含看见过、已离开视野的)的任务。没有可做任务时按房间巡逻,绝不停在原地。(普通蟹默认)',
|
|
10
|
+
create(args?: string[]) {
|
|
11
|
+
const role = args?.[0] ?? 'crab_generic';
|
|
12
|
+
let convo: ConversationGoal | null = null;
|
|
13
|
+
return new GoalRootStrategy('crab-sabotage', () => {
|
|
14
|
+
convo = new ConversationGoal(() => new CrabSabotageTop(), new SpeechModule(getSpeechConfigForRole(role)));
|
|
15
|
+
return convo;
|
|
16
|
+
}, {
|
|
17
|
+
resetOnMeetingResume: false,
|
|
18
|
+
onUpdateRole: role => convo?.updateConfig(role),
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { BehaviorDecision, CustomModule, Strategy, StrategyContext, StrategyEntry } from './types.js';
|
|
3
|
+
import type { GameState } from '../sdk/types.js';
|
|
4
|
+
import { Action } from '../sdk/action.js';
|
|
5
|
+
import { emptyKnowledgeView } from '../lib/knowledge-store.js';
|
|
6
|
+
|
|
7
|
+
function makeMoveDecision(): BehaviorDecision {
|
|
8
|
+
return { action: Action.move({ x: 100, y: 100 }) };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function makeKillDecision(): BehaviorDecision {
|
|
12
|
+
return { action: Action.kill('target') };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class TestCustomModule implements CustomModule {
|
|
16
|
+
processEventsCalled = 0;
|
|
17
|
+
afterDecideCalled = 0;
|
|
18
|
+
onMeetingResumeCalled = 0;
|
|
19
|
+
onMapLoadedCalled = 0;
|
|
20
|
+
lastTick = -1;
|
|
21
|
+
lastEventCount = 0;
|
|
22
|
+
lastRooms: any[] = [];
|
|
23
|
+
lastState: GameState | null = null;
|
|
24
|
+
lastDecisions: BehaviorDecision[] = [];
|
|
25
|
+
|
|
26
|
+
processEvents(events: any[], tick: number, state: GameState): void {
|
|
27
|
+
this.processEventsCalled++;
|
|
28
|
+
this.lastTick = tick;
|
|
29
|
+
this.lastEventCount = events.length;
|
|
30
|
+
this.lastState = state;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
afterDecide(decisions: BehaviorDecision[], _state: GameState, _ctx: StrategyContext): BehaviorDecision[] {
|
|
34
|
+
this.afterDecideCalled++;
|
|
35
|
+
this.lastDecisions = decisions;
|
|
36
|
+
return decisions;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onMeetingResume(): void {
|
|
40
|
+
this.onMeetingResumeCalled++;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onMapLoaded(rawRooms: any[]): void {
|
|
44
|
+
this.onMapLoadedCalled++;
|
|
45
|
+
this.lastRooms = rawRooms;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Module that filters out move decisions (simulates SpeechModule suppress behavior). */
|
|
50
|
+
class SuppressMoveModule implements CustomModule {
|
|
51
|
+
processEvents(_events: any[], _tick: number, _state: GameState): void {}
|
|
52
|
+
afterDecide(decisions: BehaviorDecision[], _state: GameState, _ctx: StrategyContext): BehaviorDecision[] {
|
|
53
|
+
return decisions.filter(d => d.action.type !== 'move');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class TestCustomModuleStrategy implements Strategy {
|
|
58
|
+
readonly name = 'test-custom-module';
|
|
59
|
+
private module: TestCustomModule;
|
|
60
|
+
|
|
61
|
+
constructor() {
|
|
62
|
+
this.module = new TestCustomModule();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
customModules(): CustomModule[] {
|
|
66
|
+
return [this.module];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
decide() {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const testEntry: StrategyEntry = {
|
|
75
|
+
id: 'test-custom-module',
|
|
76
|
+
description: 'Internal test strategy for CustomModule verification.',
|
|
77
|
+
create() {
|
|
78
|
+
return new TestCustomModuleStrategy();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const FAKE_STATE = { phase: 'wandering', tick: 1 } as GameState;
|
|
83
|
+
|
|
84
|
+
function makeCtx(overrides: Partial<StrategyContext> = {}): StrategyContext {
|
|
85
|
+
return {
|
|
86
|
+
taskData: [],
|
|
87
|
+
emergency: null,
|
|
88
|
+
taskLocalBlockedUntil: 0,
|
|
89
|
+
reportCorpseTarget: null,
|
|
90
|
+
reportBlockedUntil: 0,
|
|
91
|
+
notifications: [],
|
|
92
|
+
lastProgressNotifyAt: 0,
|
|
93
|
+
teammates: new Set(),
|
|
94
|
+
alarmDone: false,
|
|
95
|
+
rooms: [],
|
|
96
|
+
playerNamesBySeat: {},
|
|
97
|
+
forcePatrolAdvance: false,
|
|
98
|
+
blockedMoveTarget: null,
|
|
99
|
+
mySeat: 1,
|
|
100
|
+
speechNotifications: [],
|
|
101
|
+
agentAlerts: [],
|
|
102
|
+
knowledge: emptyKnowledgeView(),
|
|
103
|
+
...overrides,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
describe('CustomModule interface', () => {
|
|
108
|
+
describe('TestCustomModule lifecycle', () => {
|
|
109
|
+
it('processEvents receives events, tick, and state', () => {
|
|
110
|
+
const mod = new TestCustomModule();
|
|
111
|
+
const events = [{ type: 'player_moved', actor: 'p1' }, { type: 'kill', actor: 'p2' }];
|
|
112
|
+
|
|
113
|
+
mod.processEvents(events, 42, FAKE_STATE);
|
|
114
|
+
|
|
115
|
+
expect(mod.processEventsCalled).toBe(1);
|
|
116
|
+
expect(mod.lastTick).toBe(42);
|
|
117
|
+
expect(mod.lastEventCount).toBe(2);
|
|
118
|
+
expect(mod.lastState).toBe(FAKE_STATE);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('afterDecide receives and can modify decisions', () => {
|
|
122
|
+
const mod = new TestCustomModule();
|
|
123
|
+
const decisions = [makeMoveDecision(), makeKillDecision()];
|
|
124
|
+
|
|
125
|
+
const result = mod.afterDecide(decisions, FAKE_STATE, makeCtx());
|
|
126
|
+
|
|
127
|
+
expect(mod.afterDecideCalled).toBe(1);
|
|
128
|
+
expect(result).toEqual(decisions);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('onMeetingResume is callable', () => {
|
|
132
|
+
const mod = new TestCustomModule();
|
|
133
|
+
mod.onMeetingResume();
|
|
134
|
+
mod.onMeetingResume();
|
|
135
|
+
expect(mod.onMeetingResumeCalled).toBe(2);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('onMapLoaded receives raw rooms', () => {
|
|
139
|
+
const mod = new TestCustomModule();
|
|
140
|
+
const rooms = [{ name: 'room1', polygon: [[0, 0], [10, 0], [10, 10], [0, 10]] }];
|
|
141
|
+
|
|
142
|
+
mod.onMapLoaded(rooms);
|
|
143
|
+
|
|
144
|
+
expect(mod.onMapLoadedCalled).toBe(1);
|
|
145
|
+
expect(mod.lastRooms).toEqual(rooms);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Strategy.customModules()', () => {
|
|
150
|
+
it('test strategy returns modules via customModules()', () => {
|
|
151
|
+
const strat = testEntry.create();
|
|
152
|
+
expect(strat.customModules).toBeDefined();
|
|
153
|
+
|
|
154
|
+
const modules = strat.customModules!();
|
|
155
|
+
expect(modules).toHaveLength(1);
|
|
156
|
+
expect(modules[0]).toBeInstanceOf(TestCustomModule);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('customModules() returns the same instance on repeated calls', () => {
|
|
160
|
+
const strat = testEntry.create();
|
|
161
|
+
const a = strat.customModules!();
|
|
162
|
+
const b = strat.customModules!();
|
|
163
|
+
expect(a[0]).toBe(b[0]);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('Strategy-loop lifecycle simulation', () => {
|
|
168
|
+
it('simulates processEvents → afterDecide → onMeetingResume → onMapLoaded sequence', () => {
|
|
169
|
+
const strat = testEntry.create();
|
|
170
|
+
const modules = strat.customModules!();
|
|
171
|
+
const mod = modules[0] as TestCustomModule;
|
|
172
|
+
const ctx = makeCtx();
|
|
173
|
+
|
|
174
|
+
// Simulate 3 ticks: processEvents then afterDecide
|
|
175
|
+
for (let tick = 1; tick <= 3; tick++) {
|
|
176
|
+
mod.processEvents([{ type: 'player_moved' }], tick, FAKE_STATE);
|
|
177
|
+
mod.afterDecide([makeMoveDecision()], FAKE_STATE, ctx);
|
|
178
|
+
}
|
|
179
|
+
expect(mod.processEventsCalled).toBe(3);
|
|
180
|
+
expect(mod.afterDecideCalled).toBe(3);
|
|
181
|
+
expect(mod.lastTick).toBe(3);
|
|
182
|
+
|
|
183
|
+
// Simulate map load
|
|
184
|
+
mod.onMapLoaded?.([{ name: 'kitchen' }, { name: 'medbay' }]);
|
|
185
|
+
expect(mod.onMapLoadedCalled).toBe(1);
|
|
186
|
+
|
|
187
|
+
// Simulate meeting end
|
|
188
|
+
mod.onMeetingResume?.();
|
|
189
|
+
expect(mod.onMeetingResumeCalled).toBe(1);
|
|
190
|
+
|
|
191
|
+
// Post-meeting tick
|
|
192
|
+
mod.processEvents([{ type: 'session_started' }], 4, FAKE_STATE);
|
|
193
|
+
mod.afterDecide([], FAKE_STATE, ctx);
|
|
194
|
+
expect(mod.processEventsCalled).toBe(4);
|
|
195
|
+
expect(mod.afterDecideCalled).toBe(4);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('afterDecide pipeline', () => {
|
|
200
|
+
it('module can filter decisions (chained)', () => {
|
|
201
|
+
const mod = new SuppressMoveModule();
|
|
202
|
+
const decisions = [makeKillDecision(), makeMoveDecision()];
|
|
203
|
+
|
|
204
|
+
const result = mod.afterDecide(decisions, FAKE_STATE, makeCtx());
|
|
205
|
+
|
|
206
|
+
expect(result).toHaveLength(1);
|
|
207
|
+
expect(result[0].action.type).toBe('kill');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('multiple modules chain by registration order', () => {
|
|
211
|
+
const mod1 = new TestCustomModule();
|
|
212
|
+
const mod2 = new SuppressMoveModule();
|
|
213
|
+
const modules: CustomModule[] = [mod1, mod2];
|
|
214
|
+
const ctx = makeCtx();
|
|
215
|
+
|
|
216
|
+
let decisions: BehaviorDecision[] = [makeKillDecision(), makeMoveDecision()];
|
|
217
|
+
for (const mod of modules) {
|
|
218
|
+
if (mod.afterDecide) {
|
|
219
|
+
decisions = mod.afterDecide(decisions, FAKE_STATE, ctx);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// mod1 saw both decisions, mod2 filtered out move
|
|
224
|
+
expect(mod1.lastDecisions).toHaveLength(2);
|
|
225
|
+
expect(decisions).toHaveLength(1);
|
|
226
|
+
expect(decisions[0].action.type).toBe('kill');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('module without afterDecide is skipped', () => {
|
|
230
|
+
const minimal: CustomModule = {
|
|
231
|
+
processEvents(_events, _tick) {},
|
|
232
|
+
};
|
|
233
|
+
const decisions = [makeMoveDecision()];
|
|
234
|
+
const ctx = makeCtx();
|
|
235
|
+
|
|
236
|
+
// afterDecide is optional — strategy-loop skips if undefined
|
|
237
|
+
const result = minimal.afterDecide
|
|
238
|
+
? minimal.afterDecide(decisions, FAKE_STATE, ctx)
|
|
239
|
+
: decisions;
|
|
240
|
+
|
|
241
|
+
expect(result).toBe(decisions);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('CustomModule type conformance', () => {
|
|
246
|
+
it('module with only processEvents satisfies CustomModule', () => {
|
|
247
|
+
const minimal: CustomModule = {
|
|
248
|
+
processEvents(_events, _tick) {},
|
|
249
|
+
};
|
|
250
|
+
expect(minimal.processEvents).toBeDefined();
|
|
251
|
+
expect(minimal.afterDecide).toBeUndefined();
|
|
252
|
+
expect(minimal.onMeetingResume).toBeUndefined();
|
|
253
|
+
expect(minimal.onMapLoaded).toBeUndefined();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('module with all hooks satisfies CustomModule', () => {
|
|
257
|
+
const full: CustomModule = {
|
|
258
|
+
processEvents(_events, _tick) {},
|
|
259
|
+
afterDecide(d, _state, _ctx) { return d; },
|
|
260
|
+
onMeetingResume() {},
|
|
261
|
+
onMapLoaded(_rooms) {},
|
|
262
|
+
};
|
|
263
|
+
expect(full.processEvents).toBeDefined();
|
|
264
|
+
expect(full.afterDecide).toBeDefined();
|
|
265
|
+
expect(full.onMeetingResume).toBeDefined();
|
|
266
|
+
expect(full.onMapLoaded).toBeDefined();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { FindPlayerTop } from './goals/find-player-top.js';
|
|
4
|
+
import { parseTargetArgs } from './player-targets.js';
|
|
5
|
+
|
|
6
|
+
export const strategy: StrategyEntry = {
|
|
7
|
+
id: 'find-player',
|
|
8
|
+
description: '全力寻找并贴身跟踪一个指定目标(座位号或名字,单个)。看到就贴上去近距离尾随(约40距离);跟丢就先奔向最后出现的位置,再按房间顺序不停巡逻直到目标重新出现,绝不放弃。只跟踪、不出刀、不做任务、不报告;目标死亡即停。',
|
|
9
|
+
create(args?: string[]) {
|
|
10
|
+
const targets = args ? parseTargetArgs(args) : [];
|
|
11
|
+
if (targets.length === 0) {
|
|
12
|
+
throw new Error('find-player strategy requires a target seat number or player name as argument.');
|
|
13
|
+
}
|
|
14
|
+
return new GoalRootStrategy('find-player', () => new FindPlayerTop(targets[0]), { resetOnMeetingResume: false });
|
|
15
|
+
},
|
|
16
|
+
};
|