# 调试与性能

> 一拍为什么慢、命令为什么没生效、游戏为什么不动。按现象排查，再用自带的实机核对脚本确认。

来源: https://war3ai.com/docs/debugging/

## 看回执

每条命令的回执就是第一手线索：

```python
r = g.cast(hero, "blizzard", x=tx, y=ty)
if not r:
    print(r.reason, r.verdict)     # rejected（…） 以及原因码
print(r.exec_us, r.engine_us)      # 这条在游戏线程上执行了多少微秒 / 其中引擎下令函数本身花了多少
```

正常情况下一条命令在游戏线程上花几微秒到几百微秒。批量块结束后，`g.last_receipts` 是这一批每条的回执。

## 在游戏里看

```python
g.say(unit, "撤")          # 单位头顶冒一个聊天气泡（不影响游戏）
g.message("开始打野")        # 左下角消息区打一行字（只有本机看得见）
```

`print` 的内容会出现在运行 Bot 的终端里。把每拍的关键决策打印出来，配合头顶气泡，比看代码快得多。

## 一拍很慢

先看是不是这几种情况：

| 原因 | 改法 |
|---|---|
| 一条一条发命令，每条都等一帧 | 包进 `with g.batch():`，几十条只等一次 |
| 逐个调 `g.visible()` / `g.can_do()`（每次都走快车道等一帧） | 可见性用快照里的 `u.visible_to()`；可行性用 `g.can_do_many([...])` 一批问 |
| 在 `on_tick` 里 `sleep` 或等待 | 记下游戏时间，下一拍再判断 |
| 每拍重算很贵的东西（寻路、全图扫描） | 缓存结果，隔几拍再算。`g.grid()` 自带 2 秒缓存，`g.stats()` 的科技等级 5 秒缓存一次 |

## 游戏不动 / Bot 等不到进局

| 现象 | 多半是 |
|---|---|
| 一直「等进局」 | 实例号不对；或者游戏窗口**最小化**了 —— 最小化时游戏模拟是停着的（时钟不走） |
| 游戏在跑、Bot 下令没反应 | 在给别人的单位下令（回执 `not_owner`）；或者 Bot 以 observer 身份连接（`forbidden`） |
| 命令被 `held` | 这个单位被更高优先级的层握着（参考大脑的毫秒层、指挥台的手动下令），没发出去 |
| 暂停后命令还能下 | 正常：暂停时引擎时钟停住，但事件分发照跑、命令照常执行 |

## 连一下看状态

```bash
python -m openwar3 status --inst 5
```

输出连接状态：游戏 pid、世界发布周期和每次采集耗时、快车道计数、是否在局里、单位数、游戏时钟。

## 实机核对脚本

开一个演练实例，逐项核对 SDK 能力是否在你的机器上正常：

```bash
python tools/sdk_live_check.py --inst 20                 # 全部
python tools/sdk_live_check.py --inst 20 --only prod     # 只核对一节
```

分节：批量、时间、生产、排队下令、战斗属性、寻路、公平模式。每一节都会在真实对局里下命令、读回效果，打印通过数。

离线测试不需要开游戏：

```bash
python tools/run_tests.py
```

## 常见的「看起来像 bug」

- **盖房子回执接下了，但一直没地基**：树林里的点引擎也当场接下，工人走到才失败。用 `build_near`，它会跟踪并把失败的点拉黑一段时间。
- **技能回执接下了，但没放出来**：被打断了，或者没蓝。放完下一拍看 `g.cooldown()` 有没有进冷却。
- **攻击令接下了，但兵去打别人**：对具体目标攻击要用 `g.attack(兵, 敌人)`（右键语义）。原始的攻击令对目标只换订单、不记目标，会去打附近别的。
- **工人数对不上**：进了金矿的工人不在快照里。
- **死了的英雄训练不出来**：英雄唯一，要 `g.revive(祭坛)`；复活要人口，死后约 3 游戏秒才能复活。
