no roadmap. just whatever i’m curious about.
← back1998 game bot - chapter 3: determinism? determinism!

the bots are now navigating the map confidently – we are absolutely cruising. it goes without saying that now is precisely when someone might slam into a brick wall that completely deflates all momentum.

that brick wall happens to be how infantry calculates shot spread. turns out it’s not as simple as the client sending projectile information per bullet and the server just keeping track of that. the game was made in 1998. i was playing on a 56k connection. that’s roughly 5kb per second.

we know how the server works and what it expects to cross the wire, that part is simple. we need to understand how the packet is constructed – otherwise, every burst will be different on every player’s screen.

we spent a while guessing. the obvious move is to take what’s already in the packet and try to build the spread out of it — the item crc, the crc xor’d with yaw, its low byte plus a shot counter, dozens of these — then score every candidate against video of a wide-spread weapon and against the server’s own hit records. the honest result was that nothing won. a wrong model still lands the right angle often enough to look promising, and at one point an exotic formula beat the real one outright, because i’d let each candidate pick the video frame that flattered it. what broke it open was giving up on guessing and reading the decompiled client. the seed isn’t clever: it’s the low 16 bits of the shooter’s clock at the moment they pulled the trigger. the piece we’d been missing was the child word — a number sitting in the weapon’s own asset row that decides how every pellet after the first gets its seed. without it you can only predict the first pellet of a burst, which is exactly why the shotgun evidence never resolved. with it, the whole chain falls out, and the test stops being “does this correlate” and becomes “are these numbers equal.” they were.

on both machines already — never sent lcg constants next = (9301 × s + 49297) mod 233280 weapon asset row angle bounds · child word firing client the wire every other client shot tick low 16 of the trigger-pull clock server relays; stamps the same 16 bits masked out of the relayed tick packet carries item id · crc position · velocity · yaw tick no angle no projectile path the crc only names the asset row above 1 · pack s1 = lcg(tick), s2 = lcg(s1) packed = |s1 xor (s2 << 12)| 1 · pack same two steps, same mix 2 · angles low + |mix| mod (high − low) elevation: two lcg steps further 2 · angles same bounds, because the asset row is the same 3 · next pellet seed = lcg(lcg(prev)) + child word × index 3 · next pellet same child word, so the same chain identical pellet for pellet

the diagram above is the whole thing. one 16-bit tick crosses the wire — that’s the entire payload for a shot’s randomness, which is the answer to the 56k problem. the generator constants and the weapon’s asset row are already sitting on every machine, so none of it needs sending. both ends take that tick and run the same three steps on it: pack it, fold it into an angle inside the weapon’s declared cone, then derive the next pellet’s seed. same tick, same recipe, same fan on every screen. the crc in the packet isn’t part of the randomness at all — it just names which asset row i fired, so your copy and mine agree on the ingredients.