audit finding #13, the last one. summon() gave every unit of a
SummonNumber>1 card the identical command point, so goblins/archers
stacked on one spot and their positions - which are hashed - diverged
from the client, which places them on a ring.
ported LogicMath::sin/cos: SIN_TABLE (91 entries, sin(deg)*1024)
extracted verbatim from the binary, with the same quadrant folding, plus
sin/cos scaled helpers. then LogicBattle::getSpawnOffset: the per-count
ring (radius = 1000*collisionRadius / sin(180/n,1000), angle = base +
360*index/divisor + 90), with the bottom player's y mirrored. summon()
adds the offset per unit.
tests: sin(0/90/180/270)=0/1024/0/-1024, cos(0)=full scale, a single
unit gets (0,0) and a pair splits across x; goblins no longer share a
point.
residual: the per-unit deploy stagger (charData[+200]*index/count %
DeployTime) is not applied - all units still deploy_timer=DeployTime;
that field's source column is unidentified, left for later.
this closes the 17-item audit's actionable list. remaining known gaps
are the deploy stagger above and the heartbeat follow-up (bot commands
in-band + drop periodic snapshots).