Docs Reference
Receipts and reason codes
Every command's receipt carries a status code and a reason code. They're how bots and agents correct themselves, turning “why didn't it work?” into a machine-readable number.
r = g.train(barracks, "hfoo")
bool(r) # False
r.status # 1 -> rejected
r.verdict # 3 -> not enough food
r.reason # 'rejected(人口不够)' (SDK output: "rejected (not enough food)")
r.exec_us # microseconds this command took on the game thread
if r: is equivalent to r.status == 0 (the engine accepted it).
Status codes: status
| Code | Name | Meaning | Common causes |
|---|---|---|---|
| 0 | accepted | The engine accepted it | — (but accepted ≠ done; see below) |
| 1 | rejected | Rejected by the engine | See verdict |
| 2 | bad_unit | The unit doesn’t exist or its handle doesn’t match | The unit is already dead; you used a stale unit object |
| 3 | not_owner | Not your unit | Commanding someone else’s unit as a player |
| 4 | fault | Exception during execution (caught by the runtime; it won’t take down the game) | Please report it with steps to reproduce |
| 5 | bad_args | Bad arguments | Wrong coordinates, slot index or four-character code |
| 6 | unsupported | Not supported | This runtime version doesn’t have that capability |
| 7 | bad_target | Invalid target | The target is gone; wrong target type |
| 8 | forbidden | Not allowed for the lane’s role | Issuing commands as an observer |
| 97 | cancelled | An exception was raised inside a batch block, so nothing in the batch was sent | Code inside a with g.batch(): block raised an error |
| 98 | held | The unit is held by a higher-priority layer, so the command wasn’t sent | The reference brain’s reflex layer or a manual command from the console is holding the unit |
| 99 | timeout | Timed out | The deadline passed while the game was paused or lagging (expired commands are never executed) |
Reason codes: verdict
When a command is rejected, the runtime explains why using the engine’s own feasibility check. You can also ask before issuing the command: g.can_do(unit, code) returns the same codes.
| Code | Meaning | What to do |
|---|---|---|
| 0 / 220 | OK | — |
| 3 | Not enough food | Build food structures; watch g.production(b).blocked to catch it early |
| 8 | Not enough gold | Wait for gold; check g.can_afford(code) before issuing |
| 9 | Not enough lumber | Put more workers on lumber |
| 32 | Training queue full (7 slots) | Queue only 1 at a time: queue the next when g.queue(b) is empty |
| 183 | Missing prerequisite tech / building | Build the prerequisite first, or tier up |
| 185 | Building is busy | The altar is reviving a hero; the town hall can’t upgrade while its queue isn’t empty |
| 221 | Not available / under construction / upgrading / already exists | The hero already exists (use revive if it’s dead); this shop doesn’t sell that |
| 89 | Shop not stocked yet | At game start, items become available at the stock time in the item table; a newly built shop starts counting from the moment it’s finished |
| 1001 | Target not visible | The target is in fog of war or the black mask; attack_move to its position |
Accepted ≠ done
A receipt only tells you that the engine accepted the command, and it’s read back in the same frame. It can’t account for what happens afterward:
| Command | Can still fail after being accepted | How to confirm |
|---|---|---|
| Build | A spot in a forest is accepted immediately too; it fails when the worker gets there | Use build_near (it tracks whether the foundation appears), or wait for production.done |
| Cast | Interrupted, out of mana | On the next tick, check whether g.cooldown(u, ability) has started |
| Train | Queued, but never starts because there’s not enough food | g.production(b).blocked |
| Move / attack | Overridden by other logic (or a higher-priority layer) | g.current_target(u), g.order_of(u) |
Query APIs
These APIs don’t issue orders; they only ask the engine. The result is also placed in the receipt’s value (the SDK returns the value directly):
| API | Returns |
|---|---|
g.can_do(u, code) / g.can_do_many([(u, code), ...]) | The reason codes in the table above |
g.tech(code, player=None) / g.tech_many([...]) | Research level / number of completed buildings (upgrade chain included) |
g.visible(x, y) | Whether this point is visible to us |
g.gold_left(mine) | How much gold is left in a mine |
g.enemy_ai_plan(enemy_unit) | Where the computer opponent’s captain plans to lead its troops (computer AI only) |