# W3P 协议

> 运行时和外部程序之间的全部契约：八块共享内存、读世界状态、读事件、下命令、回执、车道角色、画板、界面与输入。用 Python 以外的语言接入，看这一页。

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

运行时和外部程序之间**只通过共享内存**交换数据，下面这些就是全部。

- 参考实现是 Python 的 `sdk/python/w3world.py`（读）和 `sdk/python/w3fast.py`（写），每个结构的大小和偏移都写在里面，有测试钉住；
- **协议只描述语义，和游戏版本无关。** 换游戏版本时运行时自己适配，协议不变；新字段只追加在块尾，老客户端照常能用。

> **说明**
>
> 大多数人不需要读这一页 —— 用 Python SDK 就好。只有当你想用 C++ / C# / Rust / Go 等语言直接接入，或者想知道 SDK 底下发生了什么时，才需要它。

## 1. 八块共享内存

`<pid>` 是游戏进程号。

| 名字 | 方向 | 内容 | 同步方式 |
|---|---|---|---|
| `Local\War3World_<pid>` | 运行时 → 你 | 世界状态：头 + 16 个玩家 + 最多 1024 个单位 + 256 份单位细节 + 256 个地上物品 + 扩展区 + 生产表 | seqlock |
| `Local\War3Trees_<pid>` | 运行时 → 你 | 最多 4096 个可破坏物（树等），每 2 秒刷新 | seqlock |
| `Local\War3Events_<pid>` | 运行时 → 你 | 事件环，8192 条 | 每条自带序号 |
| `Local\War3Map_<pid>` | 运行时 → 你 | 地图：地形格子（128 一格，最多 256×256）+ 可玩区边界 + 出生点；开局后几秒分批算完 | seqlock（算好后不再变） |
| `Local\War3Fast_<pid>` | 双向 | 命令车道：16 条 × 16 槽；每槽一条命令 + 回执；每条车道带角色 | 每槽单写者单读者 |
| `Local\War3Canvas_<pid>` | 你 → 运行时 | [画板](https://war3ai.com/docs/canvas/)：头 64 字节 + 256 个元素 × 112 字节 + 64 KB 文字 / 点池；发一次 `canvas_enable` 后才建 | seqlock（你写，运行时每帧读） |
| `Local\War3Msgs_<pid>` | 运行时 → 你 | 屏幕消息环：游戏提示、聊天、系统消息的全文，128 条 × 256 字节 | 每条自带序号 |
| `Local\War3Input_<pid>` | 双向 | [界面与输入](https://war3ai.com/docs/ui-input/)：运行时回写鼠标位置、指着的地面点、悬停项；你写热键表和鼠标开关；发一次 `input_enable` 后运行时才开始接管输入 | 热键表 seqlock |

**好几个客户端同时用画板和输入**：这两块都只有一份，各写各的会互相覆盖。约定如下，自己写客户端也要照做：

- **画板**：持命名互斥量 `Local\War3CanvasMutex_<pid>` 读 - 改 - 写，只换自己的元素，别人的原样留着（重排池偏移）；主人进程已退出的和没有主人的清掉。元素的 `reserved[1]` = 主人进程号、`reserved[2]` = 进程内序号；元素编号从块头偏移 60 的计数器分配（从 `0x10000` 起）。
- **输入**：每个客户端把自己的热键和鼠标开关登记在 `Local\War3InputClients_<pid>`（头 16 字节 + 16 个客户端 × 528 字节），持 `Local\War3InputMutex_<pid>` 改完自己那条，再把活着的客户端合并写进输入块：热键按「键码 + 修饰键」去重，鼠标开关取并集。事件发给所有客户端，各自按「键码 + 修饰键」认自己的热键。登记表里还有别的活客户端时，别发 `input_enable 0`。
- **运行时**：主人进程已退出的可点元素不再拦点击；每 2 秒看一次登记表，登记过的客户端全都退出了，就把输入块的热键表和鼠标开关清零。

## 2. 读世界状态（seqlock）

```text
loop:
    s1 = block.seq                (偏移 8，int32)
    if s1 是奇数: 重试            (运行时正在写)
    拷贝 头 + players + units[unitCount] + details[detailCount] + items[itemCount]
    if block.seq != s1: 重试
```

- **头**：发布计数（不涨 = 发布断了）、引擎游戏时钟、每局 +1 的 epoch、本方玩家号、是否在局里、倍速、发布周期、这一份在游戏线程上采集花的微秒数、事件序号、分段耗时。客户端可以写 `requestedPeriodMs` 请求发布周期（16 ~ 1000 ms）。
- **单位**（112 字节）：句柄对（**用句柄对认单位**，地址会复用）、类型四字码、主人、标志、坐标、血 / 蓝（含上限）、当前订单 + 订单目标、任务目标（实际在打谁）、英雄等级 / 经验 / 技能点、细节下标、`visibleTo`（位 p = 玩家 p 此刻看得见它）。
- **细节**（288 字节，英雄 > 玩家单位 > 野怪，最多 256 个）：12 个技能（码 / 等级 / 标志 / 剩余冷却秒）、8 个 buff 码、6 格背包。
- **块尾扩展**（只追加、不挪动前面的偏移，老客户端照常能用）：扩展区 `EXT1`（游戏内时间、昼夜流速、生产表条数）和生产表 `prods[128]`（正在训练 / 研究 / 建造 / 升级的建筑、队列、总时长、已进行时间、是否卡住）。**magic 对上才用。**

## 3. 读事件

```text
head = ring.writeSeq               (偏移 8)
for seq in (cursor, head]:
    e = ring.events[(seq - 1) % 8192]
    if e.seq > seq:  丢了一条（读太慢被覆盖）
    elif e.seq != seq: 还没写完，下次再读
    else: 处理 e
```

事件结构 64 字节：`seq kind clockMs addr handleLo handleHi typeId owner a b x y value extra`。

- 对比相邻两次发布得出（精度 = 发布周期）：`unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started`；
- 引擎级（运行时在游戏线程上当场记下，**每一下**都有）：`damage`（来源、伤害类型、攻击类型、位置、实际掉血、护甲前伤害）、`killed`（凶手）；
- 跟踪生产表得出：`production.done`（做完的四字码、类别、用了多少游戏秒；对手的也发）；
- 运行时每次发布顺带检查：`spell.cast`（技能开始冷却：`a` 技能四字码、`b` 等级、`value` 冷却秒、`x/y` 施法点）、`player.left`（`a` 玩家号、`b` 新的槽状态）、`selection.changed`（本机玩家的选择，全表在世界块扩展区）、`game.ended`（离开对局）；
- 屏幕消息：`message`（`a` = 消息序号，全文在共享内存 `Local\War3Msgs_<pid>` 里查：128 条 × 256 字节，游戏提示、聊天、系统消息都在；`b` = 消息框编号）；
- 界面与输入（开了 `input_enable` 之后）：`ui.click`（`a` 画板项 id、`b` 1 左键 / 2 右键）、`ui.hover`、`hotkey`（`a` 热键 id、`b` 虚拟键码）、`mouse.world`（`x/y` 地面坐标、`value` = 1 表示被吞了），修饰键都在 `extra`。

## 4. 下命令

1. **一个客户端对象占一条车道**：持 `Local\War3FastMutex_<pid>` 找一条空闲（或主人进程已死）的车道，写入角色、玩家号、自己的 pid。同一进程要两种角色就开两条；
2. 填槽：语义命令标志、操作码、`args[11]`、截止时间 `deadlineMs`；
3. 所有槽写完后标记提交，把车道的 `submitSeq` 加 1；
4. 等 `Local\War3FastDone_<pid>_<lane>` 事件（或轮询），读回执，归还槽。

运行时在游戏线程的事件分发里成批执行：一次排空的时间预算 **4 ms**（真实高精度计时），超了剩下的留到下一次分发。**过了截止时间的槽不会再执行** —— 不会出现「暂停恢复后旧命令又执行一遍」。

`args` 下标：`0..2` 单位（地址、句柄 lo、句柄 hi）、`3` 订单号或四字码、`4..6` 目标、`7/8` x / y（float 位）、`9` extra（玩家号 / 格号 / 开关 / 排队位）、`10` mode（0 无目标 / 1 对点 / 2 对目标）。

### 操作码

| 操作码 | 名字 | 说明 |
|---|---|---|
| 1 | `point` | 单位对点下令（移动 / 攻击移动 / 巡逻 / 攻击地面 / 点施法）。extra bit0 = 排队（插在当前订单后面） |
| 2 | `target` | 单位对目标下令（右键攻击 / 采集 / 修理 / 目标施法 / 捡物品）；目标必须看得见 |
| 3 | `immediate` | 无目标命令（停止 / 原地待命 / 训练 / 研究 / 升级 / 无目标施法） |
| 4 | `build` | 工人造建筑（坐标对齐 32） |
| 5 | `learn` | 英雄学技能 |
| 6 | `use_item` | 用背包第 extra 格 |
| 7 | `revive` | 祭坛复活英雄 |
| 8 | `rally` | 集结点（对点 / 对目标） |
| 9 | `buy` | 商店卖物品给旁边的英雄 |
| 10 | `item_drop` | 物品离手：给队友、卖给商店（`code` = 接收的单位），或者丢在地上 |
| 20 ~ 25 | 查询 | `q_tech` 科技计数、`q_feasible` 可行性、`q_visible` 可见性、`q_mine_gold` 金矿余量、`q_captain` 电脑队长、`q_dead_heroes` 死亡英雄表 |
| 30 | `pause` | 暂停 / 继续 |
| 40 ~ 50 | 镜头 | 读镜头状态、设字段、看向一点、跟随、复位、旋转、边界、平滑、界面显隐、干净画面、雾 |
| 60 ~ 63 | HUD | 任务按钮文字、任务面板标题与描述、刷新、读面板是否被打开 |
| 70 | `jass` | 按名字调用 JASS native（1291 个）：名字和字符串参数放在槽的附加区，其余参数按签名放进 `args`；返回值在 `value[0]`。只给本机工具车道；带函数参数或会挂起脚本线程的一律拒绝。见 [JASS 通道](https://war3ai.com/docs/jass/) |
| 71 / 72 | `jass_handle_of` / `jass_unit_of` | 快照里的单位 ↔ JASS 句柄互换（快照里的句柄对不是 JASS 句柄） |
| 73 | `canvas_enable` | 建画板共享内存、装绘制钩子；任何车道都能发（画板只画在本机画面上）。第一次要装钩子，超时给 2 秒以上 |
| 74 | `input_enable` | `extra` = 1 接管游戏窗口的输入（画板项点击 / 悬停、热键、地面点击），0 = 交还。输入块 `Local\War3Input_<pid>`：头 128 字节 + 32 条热键 × 16 字节，你写热键表和鼠标开关，运行时回写鼠标位置、指着的地面点、悬停项。任何车道都能发（只影响本机输入）。见 [界面与输入](https://war3ai.com/docs/ui-input/) |

## 5. 回执

回执 52 字节（+8 字节耗时）：`status`、`engineReturn`、`verdict`（被拒原因码）、`orderBefore / orderAfter`（同一帧读回的单位订单）、`value[8]`（查询结果）、`execUs`（这条在游戏线程上执行了多少微秒）、`engineUs`（其中引擎下令函数本身）。

全部状态码和原因码见 [回执与原因码](https://war3ai.com/docs/reason-codes/)。

## 6. 车道角色

| 角色 | 能做什么 |
|---|---|
| `dev` | 本机工具：语义命令（指挥本机玩家的单位）+ JASS 通道 |
| `player` | 只能语义命令，只能指挥车道所属玩家的单位（别人的 = `not_owner`） |
| `observer` | 只能查询、镜头、读 HUD 面板状态、开画板和本机输入；别的一律 `forbidden` |

两个 AI 对打 = 同一局里开两条 `player` 车道（player 0 / player 1）。

> **注意**
>
> 本机模式下角色是客户端自己声明的（约定，不是安全边界）。[对战平台](https://war3ai.com/arena/) 由裁判进程建车道，只把 `player` 车道交给选手。

## 7. 实测过的语义

- 右键（smart）对着敌人 = 攻击**这一个**（订单目标、任务目标都是它）；原始攻击令走目标命令只换上攻击令、不记目标，会去打附近别的；
- 引擎不许对看不见的单位下目标命令：天黑后远处营地进了迷雾，右键一律被拒（1001）；
- 建造「接下」只代表工人接了令：树林里的点也当场接下，工人走到才失败；明显被占的点当场拒绝；
- 英雄死后约 3 游戏秒才能复活；人口不够也会被拒（英雄占人口）；
- 背包里的物品不算地上物品；捡起来发 `item.removed`；
- 暂停时引擎时钟停住，但命令照常能下；
- 最小化起的游戏模拟是停着的（时钟不动）。
