Everything above exists to let one function run identically everywhere: the per-frame update. Here's what happens inside a single tick — 24 times a second — and how units, animation, and memory are organized to make it fast and reproducible.
The simulation advances in fixed logical frames — 24 per second. Each frame is a deterministic function of the last frame plus that frame's commands. Game speed changes only the wall-clock time per frame, never the logic — which is why a replay plays identically at any speed.
// bwgame.h:13083 — the whole frame, in order
void process_frame() {
recede_creep(); // Zerg creep slowly retreats
if (st.update_tiles_countdown == 0) st.update_tiles_countdown = 100;
--st.update_tiles_countdown;
update_tiles = st.update_tiles_countdown == 0; // fog-of-war every 100 frames
update_units(); // orders, movement, combat
update_bullets(); // projectiles in flight
update_thingies(); // effects, sprites, splatter
}
The order is fixed and so is the iteration order of every list inside it. That determinism-of-ordering matters as much as the fixed-point math: if two machines updated units in a different sequence, an RNG call could land on a different unit and desync.
step through process_frame() · watch the frame counter and per-stage work
A unit is a small bundle of state:
order_state counter the handler advancesUpdating one unit runs, in order: main order → secondary order (gather/repair/addon) → subunit (e.g. a tank turret) → animation:
// bwgame.h:8445
void update_unit(unit_t* u) {
update_unit_values(u); // hp/shield/energy regen, timers
execute_main_order(u); // the big order switch
execute_secondary_order(u); // gather, repair, build addon...
if (u->subunit && !ut_turret(u)) update_unit(u->subunit);
if (u->sprite && !iscript_execute_sprite(u->sprite)) u->sprite = nullptr;
}
execute_main_order (bwgame.h:7620) is a large switch over the Orders enum routing to per-order handlers: order_Die, movement+pathfinding for Move, target tracking and firing for AttackUnit/AttackMove, ConstructingBuilding, Train, plus race-specifics like ZergBirth, ZergUnitMorph, and Protoss warp-in. Each handler is a step of a state machine — it reads order_state, does a little work, maybe advances the state or pops the order queue.
A production building holds up to 5 queued units (static_vector<const unit_type_t*, 5> build_queue). OpenBW even reproduces a strange original behavior via a build_queue_limbo field — recent commits (a59617d, 0fa172f) exist specifically to replicate BW's quirky queue handling rather than "fix" it. In a determinism-faithful rewrite, matching the original's bugs is correctness: a desync against real BW would otherwise occur.
Brood War's animations aren't just frame sequences — they're programs. Each graphical image runs a bytecode script (iscript) on a little stack-less VM with a program counter. Opcodes play frames, wait, turn, fire weapons, play sounds, spawn sprites, and crucially signal the order system — so the visual animation and the game logic stay welded together and deterministic.
// data_types.h:373 — a sample of the ~44 iscript opcodes
opc_playfram, opc_wait, opc_waitrand, opc_goto, opc_call, opc_return,
opc_attack, opc_attackwith, opc_move, opc_turn1cwise, opc_turnccwise,
opc_playsnd, opc_sigorder, opc_orderdone, opc_creategasoverlays, ...
// game_types.h:392 — the VM's state per image
struct iscript_state_t {
const iscript_t::script* current_script;
size_t program_counter; // byte offset into the bytecode
size_t return_address; // for call/return
int animation; // Init, Walking, GndAttkInit, Death...
int wait; // frames to idle before next opcode
};
a stylized GndAttkInit script · watch the program counter, wait timer, and when it signals the order system
wait ties the VM to the 24fps clock deeperWhen the VM hits opc_wait N, it stores N in wait and stops. On each game frame, wait is decremented; only when it reaches zero does the VM resume executing opcodes. That's how an animation paces itself to real frames without any timers — the frame loop is the clock. Because every machine runs the same frames, every machine's animations advance in lockstep too. opc_sigorder/opc_orderdone flip flags the order state machine reads, so "the recoil animation finished" can drive "the shot fires."
Determinism extends to memory layout. The engine pre-allocates fixed-capacity object pools and threads objects onto intrusive linked lists — the list pointers live inside the objects themselves. There's no general-purpose allocator churning during a frame, no pointer addresses leaking into game logic, and allocation/free is O(1).
// game_types.h:47 — fixed-capacity pools (capacity, chunk size)
object_container<unit_t, 1700, 17> units_container;
object_container<bullet_t, 100, 10> bullets_container;
object_container<sprite_t, 2500, 25> sprites_container;
object_container<image_t, 5000, 50> images_container;
Units live on several lists at once — visible_units, hidden_units, per-player lists, cloaked_units — each via a different intrusive link, so a unit can be on the "all visible" list and its owner's list simultaneously without any extra allocation.
State is split into state_base_copyable (everything that can be snapshotted: frame number, economy, RNG state, tiles) and state_base_non_copyable (runtime lists and pools). The state_copier (bwgame.h:19551) deep-copies the world by walking the object containers by index and remapping every pointer — unit→unit, sprite→sprite — so a restored snapshot has internally consistent references. This is what lets tools and AI rewind and branch the simulation. Recent commits (a59617d) fixed the copier's handling of the build_queue_limbo quirk — even the save system must preserve quirks to stay deterministic.
Maps are decomposed into walkable regions. Long-distance pathing runs A* over the region adjacency graph (cheap — far fewer regions than tiles); short-distance pathing then produces concrete waypoints within and between adjacent regions (capped around 24 waypoints). Walkability is resolved per-tile, with partially-walkable tiles falling back to finer vf4 sub-tile flags (bwgame.h:767). A spatial index — units kept sorted on X and Y — answers "what's near this box?" for collision and targeting without scanning all 1700 units. Tiles are 32×32 px; walkability granularity is 8×8 px "walk tiles."