A register the routine never sets is still an input
What to know
2026-10-24, FactionUpdate 0x41a0fc, faction_daily_target 0x41a028, the Moth roll-out 0x41ae2f; Hardwar's economy_world.py.
Watcom's register convention keeps every register a callee does not return in, so a value a caller leaves in a register survives into the next call. Two economy routines take such a value as an input without meaning to:
- A counter that starts where the caller left it.
0x41a028counts the factories it may choose among in EDX, but pushes EDX and never clears it. Its caller last loaded EDX with the address of a local (lea edx, [esp + 0x1c], formsg_read), so the count starts at a stack address, and the draw that follows (rng_range(count - 1)) wraps in 32 bits. The pseudocode shows a plain counter; only the disassembly shows the missingxor edx, edx. The port takes the address as an input (count_start), and the emulator varies the stack pointer per case so the test covers it. In a real run the value depends on where Windows put the main thread's stack: something to read from the running game. - An output pointer that aliases a counter. The Moth roll-out passes the address of its own free-bay counter as the routine's EBX, and
0x419d20writes the bay it took there. So the counter becomes the bay index and the loop stops early. Reading the callee alone shows an output; reading the caller alone shows a counter. Follow every pointer argument back to what it points at.
How to catch them: when a routine reads a register before writing it (EDX, EBX, ECX after the pushes), find what the caller last put there, even across calls, since the calls keep it. Under the emulator, run the routine from its real caller, or vary the register, rather than calling it with clean registers: a clean EDX of 0 would have made 0x41a028 look correct.