# W3P 協定

> 執行環境和外部程式之間的全部契約：八塊共享記憶體、讀取世界狀態、讀取事件、下命令、回執、車道角色、畫板、介面與輸入。要用 Python 以外的語言接入，請看這一頁。

來源: https://war3ai.com/zh-tw/docs/protocol/

執行環境和外部程式之間**只透過共享記憶體**交換資料，下面這些就是全部。

- 參考實作是 Python 的 `sdk/python/w3world.py`（讀）和 `sdk/python/w3fast.py`（寫），每個結構的大小和位移都寫在裡面，有測試釘住；
- **協定只描述語意，和遊戲版本無關。** 換遊戲版本時由執行環境自行適配，協定不變；新欄位只追加在區塊尾端，舊用戶端照常能用。

> **說明**
>
> 大多數人不需要讀這一頁 —— 用 Python SDK 就好。只有當你想用 C++ / C# / Rust / Go 等語言直接接入，或想知道 SDK 底下發生了什麼時，才需要它。

## 1. 八塊共享記憶體

`<pid>` 是遊戲的行程 ID。

| 名稱 | 方向 | 內容 | 同步方式 |
|---|---|---|---|
| `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/zh-tw/docs/canvas/)：標頭 64 位元組 + 256 個元素 × 112 位元組 + 64 KB 文字 / 點池；送出一次 `canvas_enable` 後才建立 | seqlock（你寫入，執行環境每幀讀取） |
| `Local\War3Msgs_<pid>` | 執行環境 → 你 | 螢幕訊息環：遊戲提示、聊天、系統訊息的全文，128 筆 × 256 位元組 | 每筆自帶序號 |
| `Local\War3Input_<pid>` | 雙向 | [介面與輸入](https://war3ai.com/zh-tw/docs/ui-input/)：執行環境回寫滑鼠位置、指著的地面點、懸停項目；你寫入熱鍵表和滑鼠開關；送出一次 `input_enable` 後，執行環境才開始接管輸入 | 熱鍵表 seqlock |

**好幾個用戶端同時使用畫板和輸入**：這兩塊都只有一份，各寫各的會互相覆蓋。約定如下，自己寫用戶端也要照做：

- **畫板**：持有具名互斥鎖 `Local\War3CanvasMutex_<pid>` 進行讀取 - 修改 - 寫入，只換掉自己的元素，別人的原樣保留（重新排列池位移）；擁有者行程已結束的和沒有擁有者的就清掉。元素的 `reserved[1]` = 擁有者行程 ID、`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` 單位（位址、handle lo、handle 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/zh-tw/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/zh-tw/docs/ui-input/) |

## 5. 回執

回執 52 位元組（+8 位元組耗時）：`status`、`engineReturn`、`verdict`（被拒原因碼）、`orderBefore / orderAfter`（同一幀讀回的單位訂單）、`value[8]`（查詢結果）、`execUs`（這一條在遊戲執行緒上執行了多少微秒）、`engineUs`（其中引擎下令函式本身）。

全部狀態碼和原因碼見 [回執與原因碼](https://war3ai.com/zh-tw/docs/reason-codes/)。

## 6. 車道角色

| 角色 | 能做什麼 |
|---|---|
| `dev` | 本機工具：語意命令（指揮本機玩家的單位）+ JASS 通道 |
| `player` | 只能下語意命令，只能指揮車道所屬玩家的單位（別人的 = `not_owner`） |
| `observer` | 只能查詢、操作鏡頭、讀取 HUD 面板狀態、開啟畫板和本機輸入；其他一律 `forbidden` |

兩個 AI 對打 = 同一局裡開兩條 `player` 車道（player 0 / player 1）。

> **注意**
>
> 本機模式下，角色由用戶端自行宣告（這是約定，不是安全邊界）。[對戰平台](https://war3ai.com/zh-tw/arena/) 由裁判行程建立車道，只把 `player` 車道交給選手。

## 7. 實測過的語意

- 右鍵（smart）對著敵人 = 攻擊**這一個**（訂單目標、任務目標都是它）；原始攻擊令走目標命令只會換上攻擊令、不記錄目標，會去打附近別的單位；
- 引擎不允許對看不見的單位下目標命令：天黑後遠處營地進入迷霧，右鍵一律被拒（1001）；
- 建造「接下」只代表工人接了命令：樹林裡的點也會當場接下，工人走到才失敗；明顯被占用的點會當場拒絕；
- 英雄陣亡後約 3 遊戲秒才能復活；人口不夠也會被拒（英雄占人口）；
- 物品欄裡的物品不算地上物品；撿起來會發 `item.removed`；
- 暫停時引擎時鐘停止，但命令照常能下；
- 以最小化方式啟動的遊戲，模擬是停住的（時鐘不動）。
