Debugging and performance
Why a tick is slow, why a command didn't take effect, why the game isn't moving. Troubleshoot by symptom, then confirm with the bundled live verification scripts.
Read the receipt
Every command’s receipt is your first-hand clue:
r = g.cast(hero, "blizzard", x=tx, y=ty)
if not r:
print(r.reason, r.verdict) # rejected(…) and the reason code
print(r.exec_us, r.engine_us) # microseconds this command took on the game thread / how much of that the engine's order function itself took
Normally a command takes anywhere from a few to a few hundred microseconds on the game thread. After a batch block ends, g.last_receipts holds the receipt for each command in that batch.
Watch it in the game
g.say(unit, "Fall back") # a chat bubble pops up over the unit (doesn't affect the game)
g.message("Going creeping") # prints a line in the bottom-left message area (visible only on this machine)
Anything you print shows up in the terminal running the bot. Printing the key decisions of each tick, together with speech bubbles, is much faster than reading code.
A tick is slow
First check whether it’s one of these:
| Cause | Fix |
|---|---|
| Sending commands one at a time, each waiting a frame | Wrap them in with g.batch():, so dozens of commands wait only once |
Calling g.visible() / g.can_do() one by one (each goes through the fast lane and waits a frame) | For visibility, use u.visible_to() from the snapshot; for feasibility, ask in one batch with g.can_do_many([...]) |
Calling sleep or waiting inside on_tick | Note the game time and check again on a later tick |
| Recomputing something expensive every tick (pathfinding, full-map scans) | Cache the result and recompute every few ticks. g.grid() has a built-in 2-second cache, and g.stats() caches tech levels for 5 seconds |
The game isn’t moving / the bot never gets into the game
| Symptom | Most likely cause |
|---|---|
| Stuck on “waiting for game” | Wrong instance number; or the game window is minimized, and the game simulation stops while it’s minimized (the clock doesn’t advance) |
| The game is running, but the bot’s commands do nothing | Commanding someone else’s units (receipt not_owner); or the bot connected as an observer (forbidden) |
Commands come back held | The unit is held by a higher-priority layer (the reference brain’s reflex layer, or a manual command from the console), so the command wasn’t sent |
| Commands still go through while paused | That’s normal: when paused, the engine clock stops, but event dispatch keeps running and commands execute as usual |
Connect and check status
python -m openwar3 status --inst 5
This prints the connection status: game PID, world publish interval and per-capture time, fast lane counters, whether a game is in progress, unit count, and the game clock.
Live verification scripts
Start a test instance and check, item by item, that the SDK’s capabilities work on your machine:
python tools/sdk_live_check.py --inst 20 # everything
python tools/sdk_live_check.py --inst 20 --only prod # check just one section
Sections: batching, time, production, queued commands, combat stats, pathfinding, fair mode. Each section issues commands in a real game, reads back the effects, and prints the number of passes.
Offline tests don’t need the game running:
python tools/run_tests.py
Common “looks like a bug” cases
- The build receipt says accepted, but no foundation ever appears: the engine accepts a spot in a forest immediately too; it only fails when the worker gets there. Use
build_near, which tracks the attempt and blacklists failed spots for a while. - The spell receipt says accepted, but the spell never went off: it was interrupted, or the caster was out of mana. On the tick after casting, check whether
g.cooldown()has started. - The attack order was accepted, but the soldiers attack something else: to attack a specific target, use
g.attack(soldier, enemy)(right-click semantics). The raw attack order on a target only changes the order without recording the target, so the units go after something else nearby. - The worker count doesn’t add up: workers inside a gold mine aren’t in the snapshot.
- A dead hero can’t be trained: heroes are unique, so use
g.revive(altar). Reviving costs food, and a hero can only be revived about 3 game seconds after it dies.