# AgentDuel Agent 编写指南入口

先确认目标玩法，再读取对应模式指南。

## 游戏规则

### 地图、距离和视野

对局地图尺寸和关键点位由当前观测提供。代码通过模式观测中的 `map.width`、`map.height`、出生点、旗帜候选点和归还点适配不同地图，不假设固定宽度或固定水平出生位置。

| 地形 | 含义 |
|------|------|
| `"*"` | 平地，可以行走 |
| `"#"` | 墙体，不可行走，阻挡视野 |
| `"&"` | 草丛，可以行走，满足条件时进入隐藏 |
| `"-"` | 水坑，不可行走，不阻挡视野 |

坐标规则：

- `x` 向右增加，`y` 向下增加。
- 距离使用 Chebyshev 距离：`max(abs(a.x - b.x), abs(a.y - b.y))`。
- 视野没有距离上限，朝向不影响视野。
- 可被技能选中的单位需要存活、未隐藏，且与施法者之间没有墙体或墙角遮挡。

### 回合、AP 和同步结算

- 每回合默认 3 AP。
- 普通移动和等待消耗 1 AP。
- 技能消耗读取 `getSkillInfo(skillId).apCost`。
- 每个 tick 双方先应用免费转向，再校验行动。
- 等待与技能先结算，普通移动后结算。
- 技能阶段新施加的 `stun`、`root`、`fear` 会立即影响同 tick 的普通移动。
- `blink`、`disengage`、`charge` 属于技能阶段位移。
- 双方同时移动可能产生 `sameTarget`、`swap`、`pathCross` 或 `occupiedFinal` 碰撞。
- 每次 `decideAction()` 或 `decideActions()` 调用都基于当前 tick 的最新观测重新决策。
- 每场对战中，双方各有 2000ms 的总计代码运行时长，只统计 `decideAction()` 或 `decideActions()`；累计达到预算会因 `timeout` 判负。
- 每个大回合包含多个小节。每个小节需要决策时，己方单次 `decideAction()` 或 `decideActions()` 最多运行 100ms。
- `observation.tick` 是当前观测对应的小节编号；`onTurnStart` 收到 `0`，正式决策从 `1` 开始并在当前大回合内递增。
- `observation.usedTime` 返回本次调用开始前己方已完成决策的累计耗时，单位毫秒。首次决策为 `0`，当前调用耗时从下一次观测开始可见。

#### 普通移动碰撞

四种 `collision.reason` 只用于同一小节内已经通过校验的普通 `move`。`blink`、`disengage`、`charge` 在技能阶段结算，不进入这套普通移动碰撞判定：即使技能位移轨迹在几何上交叉，或两个单位通过技能位移互换移动前位置，也不会产生 `pathCross` 或 `swap`，不会因此退回移动前位置。技能落点仍按各自的占位规则处理；夺旗模式中多个 `charge` 争用同一落点时，相关行动记为 `invalidAction`，精确原因为 `contestedDestination`，阶段为 `chargeConflict`，不是 `collision`。普通移动的前三种碰撞按 `sameTarget`、`swap`、`pathCross` 的顺序判断：

| `collision.reason` | 触发条件 |
|--------------------|----------|
| `sameTarget` | 至少两个普通移动都把同一个非复活槽位格子作为 `intendedTo`。 |
| `swap` | 单位 A 的 `intendedTo` 等于单位 B 的移动前位置，同时单位 B 的 `intendedTo` 等于单位 A 的移动前位置。 |
| `pathCross` | 两条一步移动线段在各自内部严格交叉，典型情况是两个斜向移动形成 X；只在端点接触不算 `pathCross`，共同终点属于 `sameTarget`，互换端点属于 `swap`。 |
| `occupiedFinal` | 前三种都未命中，但应用占位阻塞后，两个候选最终位置仍会重合时使用的兜底冲突。 |

产生 `collision` 后，事件中的 `unitIds` 对应 `positions`；`positions` 是各单位移动前的位置，`moves` 存在时，其 `from` 和 `intendedTo` 记录原位置与计划终点。所有碰撞单位都留在各自的 `positions`，不会为该行动产生 `move` 事件；已经通过校验的移动仍照常消耗行动力，不会因为碰撞返还。

夺旗模式的 `respawnSlots` 允许单位同格，因此以复活槽位为目标时跳过普通占位碰撞。目标格被静止单位占据，或前方移动失败造成级联占位时，事件类型是 `moveBlocked`，原因分别为 `occupiedByStationaryUnit` 或 `dependencyBlocked`，不属于上述四种 `collision.reason`。

### 行动格式

每个单位在每个 tick 提交一个 `Action`：

```ts
type ActionMetadata = {
  memory?: string;
  facing?: Direction;
};

type Action = ActionMetadata &
  (
    | { type: "move"; direction: Direction }
    | { type: "wait" }
    | { type: "giveup" }
    | {
        type: "skill";
        skillId: string;
        targetId?: string;
        targetPosition?: Position;
        direction?: Direction;
      }
  );
```

- `memory` 更新己方私有记忆槽，最大 1024 个 JS 字符。
- `facing` 免费转向，不消耗 AP。
- 普通移动未填写 `facing` 时，朝向自动变成移动方向。
- `blink` 的 `direction` 与转向后的 `facing` 保持一致。
- `disengage` 使用当前 `facing` 计算后撤方向。
- `{ type: "giveup" }` 会立即弃权。
- 非法行动会记录 `invalidAction` 并回退为消耗 1 AP 的等待。

### 状态、地图效果和事件

常用状态：

| 状态 | 含义 |
|------|------|
| `slow` | 下一回合可用 AP 减少 |
| `stun` | 限制行动 |
| `fear` | 限制朝威胁来源靠近 |
| `silence` | 不能释放非普通攻击技能 |
| `root` | 不能普通移动 |
| `bleed` | 回合开始受到持续伤害 |
| `burn` | 下一回合开始受到 1 点伤害 |
| `toxin` | 下一回合可用 AP 减少 |
| `berserk` | 造成和受到的伤害都增加 |

状态判断读取 `Buff.kind` 或 `Debuff.kind`。

地图效果判断先读 `effect.type`，再读对应字段：

```ts
if (effect.type === "buffPickup" && effect.kind === "berserk") {
  const target = effect.position;
}

if (effect.type === "trap" && effect.ownerSide === observation.self.side) {
  const trapPosition = effect.position;
}
```

`lastEvents` 只包含已经结算且对 Agent 可见的结构化事件。回放审计事件 `agentDecision`、双方决策耗时、审计用 `visibleEnemyIds`、`stateSnapshot` 和 v2 精确失败诊断不会进入该数组；对手的 `invalidAction` 不下发，己方 `invalidAction` 只保留脱敏动作和兼容分类，不包含 `memory`、异常对象或不可见目标位置。敌方放置的冰冻陷阱不会出现在 `visibleEffects` 中，对应的 `freezingTrap` 释放、`trapPlaced` 放置和冷却事件在触发前也不会进入己方 `lastEvents`；`trapTriggered` 触发事件正常可见。读取事件专属字段前先检查 `event.type`：

```ts
if (event.type === "skill") {
  const actorId = event.unitId;
  const skillId = event.skillId;
}
```

事件是历史结果，不代表不可见敌人的当前坐标。敌人不可见时，使用对应模式的 `getLastKnownEnemyPosition()`。

可见敌人的 `cooldowns` 只包含允许公开的当前技能冷却。`freezingTrap` 始终不会出现在敌方 `cooldowns` 中，不能根据冷却键或剩余回合推断隐藏陷阱。

### 防止怠战和平局

- 任意一方连续 10 回合整回合只有 `wait`，会因 `inactivity` 判平。
- 死斗连续 15 回合无伤害会判平。
- 夺旗连续 30 回合无技能释放会判平。
- 找不到敌人时执行搜索、靠近目标点或调整站位。
- 远程职业贴脸时优先拉开距离。
- 发生碰撞后切换目标点或移动方向。

## 职业设定

`ClassId` 取值为 `"warrior"`、`"mage"`、`"hunter"`。技能决策使用英文 `skillId`，显示名称不参与策略判断。释放技能前使用对应模式的 `canUseSkill()` 校验；AP、冷却、目标类型和射程可通过 `getSkillInfo(skillId)` 读取。

`getSkillInfo(skillId)` 返回以下公共结构：

下面的 `SkillTargetType`、`SkillRange` 和 `SkillEffect` 用于展开说明 `SkillInfo` 的字段结构；系统自动注入的可直接引用类型名是 `SkillInfo`。

```ts
type SkillTargetType = "unit" | "position" | "self" | "direction";
type SkillRange = number | { min: number; max: number };

type SkillEffect =
  | { type: "damage"; amount: number }
  | { type: "buff"; kind: TimedEffectKind; durationTurns: number }
  | { type: "debuff"; kind: TimedEffectKind; durationTurns: number };

interface SkillInfo {
  id: string;
  classId: ClassId;
  name?: string;
  apCost: number;
  cooldownTurns: number;
  targetType: SkillTargetType;
  range?: SkillRange;
  requiresLineOfSight: boolean;
  isMovement: boolean;
  isBasicAttack: boolean;
  effects?: SkillEffect[];
  damage?: number;
  durationTurns?: number;
  trapDurationTurns?: number;
}
```

- `cooldownTurns` 是 `SkillInfo` 中技能释放后设置的冷却回合数；`cooldowns` 则是单位观测对象上的 `Record<string, number>`，它不是数组，键是 `skillId`，值是当前剩余冷却回合数。死斗读取 `observation.self.cooldowns[skillId]` 或 `observation.visibleEnemy?.cooldowns[skillId]`，夺旗读取 `observation.selfUnits[i].cooldowns[skillId]` 或 `observation.visibleEnemies[i].cooldowns[skillId]`。敌方单位的 `cooldowns` 不包含 `freezingTrap`。
- `range` 为单个最大距离或 `{ min, max }` 区间；省略时按技能目标规则处理。
- `effects` 描述伤害、增益和减益；持续状态的回合数读取对应效果的 `durationTurns`。
- `name` 只用于显示，策略判断和行动提交始终使用 `id`。

### 战士

| skillId | 目标类型 | AP | CD | 射程 | 效果 |
|---------|----------|---:|---:|------|------|
| `charge` | `unit` | 3 | 3 | 2 | 冲锋位移并施加 `stun` 1 回合，不造成伤害 |
| `hamstring` | `unit` | 2 | 2 | 1 | 施加 `slow` 2 回合 |
| `intimidatingShout` | `unit` | 3 | 4 | 2 | 施加 `fear` 1 回合 |
| `bleed` | `unit` | 2 | 4 | 1 | 造成 1 伤害并施加 `bleed` 1 回合 |
| `basicAttack` | `unit` | 3 | 0 | 1 | 造成 2 伤害 |

### 法师

| skillId | 目标类型 | AP | CD | 射程 | 效果 |
|---------|----------|---:|---:|------|------|
| `frostbolt` | `unit` | 2 | 2 | <= 4 | 造成 1 伤害并施加 `slow` 2 回合 |
| `fireball` | `unit` | 3 | 2 | <= 4 | 造成 2 伤害并施加 `burn` 1 回合 |
| `blink` | `direction` | 3 | 3 | 朝向 | 沿当前朝向最多位移 3 格，遇阻提前停止 |
| `frostNova` | `self` | 2 | 4 | 周围 1 格 | 造成 2 伤害并施加 `root` 1 回合 |
| `wandAttack` | `unit` | 2 | 0 | <= 2 | 造成 2 伤害 |

### 猎人

| skillId | 目标类型 | AP | CD | 射程 | 效果 |
|---------|----------|---:|---:|------|------|
| `silencingShot` | `unit` | 3 | 3 | 2-4 | 造成 1 伤害并施加 `silence` 1 回合 |
| `serpentSting` | `unit` | 3 | 4 | 2-4 | 造成 1 伤害并施加 `toxin` 1 回合 |
| `disengage` | `self` | 2 | 2 | 自身 | 需要至少一个可见敌人，沿当前朝向的反方向最多后撤 3 格 |
| `freezingTrap` | `position` | 2 | 4 | 1 | 放置持续 3 回合且对敌方不可见的陷阱，触发后施加 `root` 1 回合 |
| `bowAttack` | `unit` | 2 | 0 | 2-4 | 造成 2 伤害 |

猎人的 `silencingShot`、`serpentSting` 和 `bowAttack` 最小射程都是 2；距离对方1的时候为盲区，无法释放弓箭普攻或攻击技能。

## 游戏模式

生成代码前先确定目标玩法，再继续读取一份对应模式指南：

| 模式 | 继续读取 | 观测对象 | 固定导出 |
|------|----------|----------|----------|
| 1v1 死斗 | [DEATHMATCH_AGENT_GUIDE.md](./DEATHMATCH_AGENT_GUIDE.md) | `Observation & ObservationHelpers` | `classId`、`agent` |
| 2v2 夺旗 | [CTF_TEAM_AGENT_GUIDE.md](./CTF_TEAM_AGENT_GUIDE.md) | `TeamObservation & TeamObservationHelpers` | `teamAgent` |

本文只定义两种模式共用的规则。死斗完整示例和专用约束只在死斗指南中维护；夺旗完整示例和专用约束只在夺旗指南中维护。

完成流程按以下顺序执行：

1. 只读取表中与目标玩法对应的一份模式指南，并按其中的固定导出、字段速查和示例编写源码。
2. 再读取 [AGENT_SUBMISSION_GUIDE.md](./AGENT_SUBMISSION_GUIDE.md)，先预检源码并根据诊断修复，直到预检状态为 `compiled`。
3. 使用预检成功的同一份源码和回执正式提交，轮询正式版本直到状态为 `compiled`。
4. 按提交指南使用响应中的角色或队伍 public ID 返回开始对战链接，让用户可以直接回到 AgentDuel 网站发起对战。

源码预检、提交、编译状态轮询、完成后的用户交接和创建对战的 HTTP 调用只在提交指南中维护。这些调用由外部脚本或平台调用方执行，不写进对战中的 Agent 源码。

## 代码编写指南

### 严格 TypeScript

服务端编译器会自动注入公开类型和工具函数，生成源码时保持单文件提交：

1. 不写 `import`、`require()`、动态 `import()` 或重导出。
2. 不写 `// @ts-nocheck`、`// @ts-ignore`、`// @ts-expect-error`。
3. 使用明确类型，不用 `any` 或无依据的类型断言掩盖错误。
4. 使用模式指南规定的固定导出，不用 `export default`。
5. 单 tick 在 100ms 内返回，循环必须有明确上界。

服务端严格编译至少包含：`strict`、`noImplicitAny`、`noUncheckedIndexedAccess`、`exactOptionalPropertyTypes`。

在 `noUncheckedIndexedAccess` 下，数组下标读取会得到 `T | undefined`。遍历方向时使用系统注入的 `shuffledDirections(random)`：

```ts
for (const direction of shuffledDirections(random)) {
  if (observation.canMove(direction)) {
    return { type: "move", direction };
  }
}
```

读取数组第一项时先保存并判断：

```ts
const first = items[0];
if (first !== undefined) {
  return first;
}
```

### 确定性和沙箱

系统会向 Agent 工厂传入预初始化的 `random: () => number`。需要随机行为时使用这个函数：

```ts
function chooseIndex(length: number, random: () => number): number {
  return Math.floor(random() * length);
}
```

沙箱内代码只使用纯计算、当前观测和系统注入工具。沙箱会拒绝时间、系统随机、网络、文件系统、进程、Worker、动态代码生成、浏览器 API 和前端框架。

### 公共基础类型

以下 16 个类型由死斗和夺旗源码共同自动注入，可以直接作为第三方 Agent 的类型使用：

| 类型 | 用途 |
|------|------|
| `Side` | 阵营 |
| `ClassId` | 职业 |
| `Position` | 地图坐标 |
| `Direction` | 八方向 |
| `Terrain` | 地形格子 |
| `Action` | 单位在当前小节提交的行动，完整结构见“行动格式” |
| `SkillInfo` | 技能规则，完整结构见“职业设定” |
| `TimedEffectKind` | 增益和减益种类 |
| `Buff` | 单位增益 |
| `Debuff` | 单位减益 |
| `TrapEffect` | 己方可见陷阱效果 |
| `TimedEffect` | 地图上的持续效果 |
| `BuffPickupEffect` | 狂暴拾取点 |
| `Effect` | `TrapEffect`、`TimedEffect` 和 `BuffPickupEffect` 的联合类型 |
| `VisibleEnemy` | 两种模式共用的可见敌人结构 |
| `EventLog` | `lastEvents` 中按 `type` 区分的事件联合类型 |

基础值类型：

```ts
type Side = "red" | "blue";
type ClassId = "warrior" | "mage" | "hunter";
type Terrain = "*" | "#" | "&" | "-";

interface Position {
  x: number;
  y: number;
}

type Direction =
  | "up"
  | "down"
  | "left"
  | "right"
  | "upLeft"
  | "upRight"
  | "downLeft"
  | "downRight";
```

状态和地图效果类型：

```ts
type TimedEffectKind =
  | "bleed"
  | "burn"
  | "slow"
  | "stun"
  | "fear"
  | "silence"
  | "root"
  | "toxin"
  | "berserk";

interface Buff {
  id: string;
  kind: TimedEffectKind;
  sourceUnitId: string;
  sourceSkillId: string;
  remainingTurns: number;
  appliedTurn: number;
}

interface Debuff {
  id: string;
  kind: TimedEffectKind;
  sourceUnitId: string;
  sourceSkillId: string;
  remainingTurns: number;
  appliedTurn: number;
}

interface TrapEffect {
  id: string;
  type: "trap";
  ownerSide: Side;
  position: Position;
  remainingTurns: number;
}

interface TimedEffect {
  id: string;
  type: "timed";
  unitId: string;
  kind: TimedEffectKind;
  remainingTurns: number;
}

interface BuffPickupEffect {
  id: string;
  type: "buffPickup";
  kind: "berserk";
  position: Position;
}

type Effect = TrapEffect | TimedEffect | BuffPickupEffect;
```

可见敌人类型：

```ts
interface VisibleEnemy {
  id: string;
  side: Side;
  classId: ClassId;
  hp: number;
  position: Position;
  facing: Direction;
  cooldowns: Record<string, number>;
  visibleBuffs: Buff[];
  visibleDebuffs: Debuff[];
}
```

`EventLog` 是事件联合类型。所有事件都有 `turn: number` 和 `tick: number`；读取 `lastEvents` 时先判断 `event.type`，再读取下表中的专属字段：

| `event.type` | 专属字段 |
|--------------|----------|
| `move` | `unitId`、`side`、`classId?`、`from`、`to` |
| `facing` | `unitId`、`side`、`classId?`、`from`、`to` |
| `wait` | `unitId`、`side`、`classId?`、`reason?`、`source?` |
| `skill` | `unitId`、`side`、`classId`、`skillId`、`targetId?`、`targetSide?`、`targetClassId?`、`targetPosition?`、`direction?` |
| `damage` | `sourceUnitId`、`sourceSide?`、`sourceClassId?`、`targetUnitId`、`targetSide?`、`targetClassId?`、`amount`、`hpAfter`、`sourceEffectKind?` |
| `cooldown` | `unitId`、`side?`、`classId?`、`skillId`、`remainingTurns` |
| `statusActive` | `unitId`、`side`、`classId`、`buff?`、`debuff?` |
| `buffApplied` | `unitId`、`side?`、`classId?`、`buff` |
| `debuffApplied` | `unitId`、`side?`、`classId?`、`debuff` |
| `enterGrass` | `unitId`、`side`、`classId?`、`position` |
| `hidden` | `unitId`、`side`、`classId?`、`remainingTurns` |
| `hiddenExpired` | `unitId`、`side`、`classId?`、`position` |
| `revealed` | `unitId`、`side`、`classId?`、`byUnitId`、`bySide?`、`byClassId?`、`canBeAttackedThisTurn` |
| `trapPlaced` | `unitId`、`side`、`classId?`、`position`、`trapId` |
| `trapTriggered` | `trapId`、`triggeredByUnitId`、`triggeredBySide?`、`triggeredByClassId?` |
| `buffPickupPlaced` | `pickupId`、`kind`、`position` |
| `buffPickupCollected` | `pickupId`、`unitId`、`side`、`classId?`、`kind` |
| `flagPickedUp` | `unitId`、`side`、`classId?`、`position` |
| `flagDropped` | `unitId`、`side`、`classId?`、`position` |
| `flagScored` | `unitId`、`side`、`classId?`、`score` |
| `flagReset` | `position` |
| `unitRespawned` | `unitId`、`side`、`classId?`、`position` |
| `collision` | `unitIds`、`unitSides?`、`unitClassIds?`、`reason`、`positions`、`moves?` |
| `invalidAction` | `unitId`、`side`、`classId?`、`action`、`reason`、`preciseReason?`、`stage?`、`details?` |
| `moveBlocked` | `unitId`、`side`、`classId?`、`from`、`intendedTo`、`reason`、`blockingUnitIds` |
| `death` | `unitId`、`side`、`classId?` |
| `gameEnd` | `winner`、`reason` |
| `stalemateRevealed` | `threshold` |

`agentDecision` 和 `stateSnapshot` 不会进入 `lastEvents`。对手的 `invalidAction` 不可见；虽然 `EventLog` 联合类型中保留了完整 `invalidAction` 结构，但观测中的己方事件不会携带 `preciseReason`、`stage` 或 `details`。敌方未触发的 `freezingTrap` 对应 `skill`、`cooldown` 和 `trapPlaced` 事件不可见，`trapTriggered` 在触发后可见。

### 系统自动注入的公共内容

当前 Agent 契约 `0.1.0` 会向死斗和夺旗源码共同注入以下 10 个运行时名称。源码中直接使用，不写 `import` 或重复声明：

| 名称 | 类型或签名 | 用途和边界 |
|------|------------|------------|
| `DIRECTIONS` | `readonly Direction[]` | 固定包含全部八个方向；适合完整遍历。需要随机顺序时使用 `shuffledDirections(random)`。 |
| `OPPOSITE_DIRECTION` | `Readonly<Record<Direction, Direction>>` | 按方向键读取反方向，例如 `OPPOSITE_DIRECTION["left"]` 为 `"right"`。 |
| `ATTACK_SKILLS` | `Record<ClassId, readonly string[]>` | 按职业读取推荐攻击技能顺序，例如 `ATTACK_SKILLS[self.classId]`；实际释放前仍调用对应模式的 `canUseSkill()`。 |
| `samePos` | `(a: Position, b: Position) => boolean` | 比较两个坐标的 `x`、`y` 是否都相等。 |
| `enemySide` | `(side: Side) => Side` | `red` 返回 `blue`，`blue` 返回 `red`。 |
| `shuffledDirections` | `(random: () => number) => Direction[]` | 使用 Agent 工厂收到的 `random` 打乱八方向并返回新数组，不修改 `DIRECTIONS`。 |
| `clonePosition` | `(position: Position) => Position` | 返回新的 `{ x, y }` 坐标对象，避免复用原对象引用。 |
| `directionTo` | `(from: Position, to: Position) => Direction` | 返回从 `from` 指向 `to` 的八方向；两个坐标相同时返回 `"right"`。 |
| `findNearest` | `<T extends { position: Position }>(from: Position, candidates: readonly T[], maxDistance?: number) => T \| null` | 按 Chebyshev 距离返回最近候选；没有候选或都超过 `maxDistance` 时返回 `null`。距离相同时保留数组中靠前的候选。 |
| `nearestPosition` | `(from: Position, targets: readonly Position[]) => Position` | 按 Chebyshev 距离返回最近坐标；空数组返回传入的 `from`，距离相同时保留数组中靠前的坐标。 |

精确声明如下：

```ts
declare const DIRECTIONS: readonly Direction[];
declare const OPPOSITE_DIRECTION: Readonly<Record<Direction, Direction>>;
declare const ATTACK_SKILLS: Record<ClassId, readonly string[]>;

declare function samePos(a: Position, b: Position): boolean;
declare function enemySide(side: Side): Side;
declare function shuffledDirections(random: () => number): Direction[];
declare function clonePosition(position: Position): Position;
declare function directionTo(from: Position, to: Position): Direction;
declare function findNearest<T extends { position: Position }>(
  from: Position,
  candidates: readonly T[],
  maxDistance?: number
): T | null;
declare function nearestPosition(
  from: Position,
  targets: readonly Position[]
): Position;
```

`random` 不是全局函数，而是系统传给 `agent({ random })` 或 `teamAgent({ random })` 工厂的参数；把它传给 `shuffledDirections(random)` 等需要随机源的工具。`createSeededRandom` 虽由框架内部导出，但不会自动注入第三方 Agent 源码。

`SPAWN_POINTS` 和 `FLAG_CENTER` 只在 `legacy-dev` 旧契约中注入，当前 `0.1.0` 源码不使用这两个名称。死斗出生点读取 `observation.map.spawnPoints`；夺旗目标点读取 `observation.objective.flagReturnPoints` 和 `observation.objective.flagSpawnPoints`。

模式专用工具不属于本节：死斗的 `moveToward`、`searchAction` 从死斗指南读取；夺旗的 `moveTowardTeam`、`randomMoveTeam`、`orderUnitsByClass`、`nearestUnitTo`、`nearestVisibleEnemy` 从夺旗指南读取。观测辅助方法也只按对应模式指南的签名使用。

### 字段使用规范

生成代码时只使用本文和模式指南明确列出的字段：

- 状态判断读取 `Buff.kind` 或 `Debuff.kind`。
- 狂暴拾取点读取 `BuffPickupEffect.kind`，取值为 `"berserk"`。
- 陷阱归属读取 `TrapEffect.ownerSide`。
- 技能事件先判断 `event.type === "skill"`，再读取 `event.unitId` 和 `event.skillId`。
- 读取 `EventLog` 专属字段前先检查 `event.type`。

字段不确定时，优先写更简单的策略，并回到模式指南的字段速查表确认。

### 公共提交前检查

提交源码前确认：

1. 已读取本文，并按目标玩法读取一份模式指南。
2. 固定导出名和目标模式一致。
3. 技能释放前调用对应模式的 `canUseSkill()`。
4. 普通移动前调用对应模式的移动辅助方法。
5. 不根据不可见敌人的当前位置做决策。
6. `wait` 只作为无合法技能、无目标移动、无其他合法移动时的兜底。
7. 状态、地图效果和事件字段均按字段速查表读取。
8. 已按提交指南完成预检，并保留 `compiled` 响应中的回执供正式提交使用。
