CUnit reads by member name, so it looks like an ordinary struct. One range of it is not, and a unit loop written without knowing that compiles, never crashes, and reads the wrong value forever.
The 32 bytes from 0xC0 to 0xDF are a union: the same bytes overlaid six ways, and which reading is right depends on what kind of unit it is. Nothing inside the struct says which.
| 벌처 | spiderMineCount - how many mines are left. |
|---|---|
| 캐리어 · 리버 | inHangarChild - the first unit inside the hangar. |
| 인터셉터 · 스캐럽 | parent - the unit it came out of. |
| 건물 | addon - the attached add-on. |
| 일꾼 | powerup - whatever it is carrying. |
| 비콘 | its own use. |
So a loop over every unit that reads unit.addon reads garbage for everything that is not a building - a worker's powerup pointer, a Carrier's hangar child. Filter on the unit type, or on the IsBuilding status flag, before touching anything in this range. The compiler will not stop you: the members all hang off the same class and it has no idea which unit you are holding.
The union ends at 0xDC. statusFlags and the timers after it mean the same thing on every unit again.
The second is flags. A flag member can be one bit or several, and it does not read or write like a number.
| 읽기 | 1 only when every bit in the mask is set, otherwise 0. |
|---|---|
| 0 대입 | Clears every bit in the mask. |
| 0이 아닌 값 대입 | Sets every bit in the mask. |
movementFlags.HoverUnit is three bits (0xC1), which makes the point: there is no way to read back a partly-set state, and you cannot store a number in a flag. When you need the raw bits, use check_status_flag(value, mask) and set_status_flag(value, mask), which take a mask and compare or assign that region exactly.
The third is not an error but a missed opportunity. The struct has several holes left by alignment, and shipped maps use them as a per-unit variable - 1,700 of them, with no array and no lookup.
| 0x26 (1바이트) | Between flingyID and flingyMovementType. |
|---|---|
| 0x52 (2바이트) | Alignment padding after orderUnitType. |
| 0x66 (2바이트) | Alignment padding after unitType - the one most often used. |
| 0x86 · 0x8C · 0x106 (1~2바이트) | Fields nobody has named. |
| 0x125 (1바이트) | unusedTimer - possibly meant for afterburner or the Ultralisk roar. |
“unknown” means unknown to whoever named it, not proven unused. Test the one you pick with the unit types you actually spawn. The names are eudplib's, too, so another eudplib may not have them.
To give a hole a readable name, you cannot assign a member onto CUnit - the class has __slots__. Subclass it in a .py file and declare Member(0x66, MemberKind.WORD).
Structure offsets