SCRMaps
/
Sign in

The order of one frame

Triggers run last in the frame. Almost every "why is it one beat late?" ends with that one line.

10 min read

Building a map you keep running into an unexplained beat. You order a kill and the unit is still alive this cycle; you create a unit and this cycle's Bring does not see it; you fire RunAIScript and nothing appears to happen. All three have the same cause.

A StarCraft frame is divided into eight phases, and the trigger loop is the last of them. By the time your code runs, that frame's movement, orders, death processing and projectile maths are all already done.

The eight phases of a frame
0Creep, AI scripts, queued orders, Disruption Web
1Dead units are actually cleared - the only moment a slot frees
2Visible units move; scanner and revealer vision; selection circles and cloaking
3Visible units' orders, remove timers, burning Terran buildings, melee damage
4Hidden units' orders - in transports, in production, inside a gas building
5Scanner Sweep and Map Revealer orders
6Bullets, flingies, thingies - everything that flies as a projectile
7The trigger loop - your code

The order explains all three delays. KillUnit does not kill: it only sets the order to 0 (Die). Orders are processed at phase 3, and phase 3 is already behind you - so the unit dies next frame. For the same reason, damage a projectile computes at phase 6 kills nobody that frame.

RunAIScript is the same. The action does not run the script, it creates a thread. The thread first runs at phase 0 - the start of the next frame.

CreateUnit, by contrast, does make the unit immediately inside the trigger loop. What is late is not the unit but the condition that counts it: the index Bring uses was already rebuilt this frame, so a unit you just made is first seen by next cycle's Bring.

And the fact that slots free at phase 1 only is the limit you hit most often in practice. While the trigger loop runs, not one dead unit's slot comes back. So with three slots left, you can create three units this cycle no matter how many you removed in it.

A unit is always on exactly one of five lists - free, visible, hidden, scanner-type, dying. Everything above is really a question of which phase moves a unit onto which list.

The five lists, and when a unit crosses between them
빈 칸 → 숨은A unit starts training; a nuke, interceptor or scarab starts production
숨은 → 보이는Production completes
빈 칸 → 보이는A building is placed; CreateUnit in a trigger
보이는 → 숨은A worker enters a gas building; an interceptor returns; RemoveUnit
보이는 → 숨은 → 보이는A Nydus Canal trip
보이는 → 죽는 중Order 0, the remove timer expiring, a Terran building burning down, melee damage
죽는 중 → 빈 칸At phase 1 only, after the death animation ends and the sprite is gone

The new-unit iterator (EUDLoopNewCUnit) walks from the head of the list and gives up after a few consecutive units it already knows. But a unit that took a Nydus, and a powerup picked straight back up, are re-linked to the head of the list without being new. Two of those in one frame and it skips every genuinely new unit behind them. If your system creates several units a cycle, raise the allowance: EUDLoopNewCUnit(20).

All of this assumes the hyper triggers actually run every frame. A Wait, or a countdown timer, breaks that assumption - and that is where code tracking the lists frame by frame quietly starts being wrong.

Everything is a triggerHyper triggersHolding on to many units
Sign in to track your progress