All projects
Personal

Game Automation 2025

A distributed bot fleet built to beat cheaters to a rare, randomly-spawning boss. Ten scout clients read the spawn signal from game memory and report over WebSockets; a Python coordinator then sends a separate main account to the right server to kill it.

Role
Solo developerReverse engineering, scouts & coordinator
Timeline
2025
Fleet size
10 scouts + 1 main
Stack
Lua · Python · WebSockets · Grafana
01 · Context

Context & problem

The game was grindy by design. The boss I cared about spawned rarely and at random, and the drop was worth enough that cheaters were camping it. They would find the spawn, kill it, and move on before a normal player could finish hopping servers. Prices for the drop kept climbing, and the cheaters controlled most of the supply.

Doing it manually was a losing game. You had to join server after server, check whether the boss was up, and hope nobody else got there first. The moment you looked away, the spawn was gone. A single script on one server would not help either, because the boss could be anywhere. I needed coverage across many servers at once, and I needed it to run without me.

So I built a small fleet: ten scout clients watching servers, and one separate main account for the kill. It was a personal project, not a service. The point was to automate the hunt and learn how far I could push the game client and a simple coordinator.

02 · Role

Role & scope

I built the whole thing myself: the reverse-engineering work, the Lua scripts that drive the scouts and the main account, the Python WebSocket coordinator, and the Grafana dashboards.

The key piece was finding a memory value in the game client that exposed whether the boss was present. That gave the scouts something reliable to read. Everything else was plumbing: hop servers, read the value, report, assign, join, kill.

I did not build the game; my work was the automation layer around it.

03 · Architecture

How the system fits together

There are two paths. The control path is the coordinator deciding what to do; the data path is the game clients actually doing it. Scouts are cheap and disposable. The main account is the only one that needs to succeed.

Each scout runs a Lua script inside a game client. It joins a server, reads the memory value, and if the boss is up, sends a WebSocket message to the coordinator. The coordinator keeps the live reports, collapses duplicates for the same server, and assigns the main account to one target. The main account joins that server, travels to the boss, and runs the kill script. Once it is in, the scouts hand off and disconnect.

The coordinator is deliberately the only component allowed to make decisions. A scout cannot summon the main account on its own, and the main account does not choose a server. That keeps the state in one place and makes failures easier to reason about.

The coordinator's metrics land in Grafana. The dashboards track how many bosses each account found, kills per hour, and the main account's join rate. That last one is a simple health check: if the main account stops joining servers, the pipeline is stuck somewhere.

04 · Deep dives

The parts that took real expertise

Reverse engineering the spawn signal

Most of the difficulty was not in the bot; it was in finding a signal worth trusting. The game did not expose the boss through a clean API. I used reverse engineering to locate a memory value that changed when the boss was present, then built the scout logic around reading and validating that value.

The offset is fragile. Game updates can move it, and a wrong read looks exactly like a server with no boss. I kept the memory-reading code isolated from the rest of the scout so it could be re-pointed without rewriting the fleet. That separation is the main reason the system stayed usable.

Ten scouts, one job

The fleet is ten game clients, each running the same Lua scout. A scout joins a server, checks the memory value, and either reports a spawn or moves on. The point is coverage: ten clients can watch ten servers at once, which is far more than I could check by hand.

Scouts are intentionally disposable. If one crashes or gets stuck, the coordinator does not care; it only cares about the next live report. Each scout holds a WebSocket connection to the coordinator, so a report arrives without polling. When the main account is on its way, the scout that found the boss hands off and disconnects.

The coordinator

The coordinator is a small Python WebSocket server. It accepts connections from scouts and the main account, keeps track of which servers have a live report, and decides when to dispatch. The state machine is simple: a report comes in, the server is marked as a target, the main account is assigned, and the target is cleared when the run finishes or fails.

The important rule is that reports are not commands. A scout can say “the boss is here”; it cannot say “join now”. The coordinator deduplicates repeat reports for the same server and makes sure only one main-account run is active at a time. That is what keeps two scouts from sending the same account to the same boss twice.

The main account

The main account is a separate client with a different Lua script. It receives a server assignment, joins, travels to the boss, and runs the kill sequence. The scout that reported the spawn disconnects once the main account is in, which frees the slot and keeps the fleet from piling up on one server.

Timing is the whole game. The main account has to join and reach the boss before the spawn despawns or someone else kills it. I kept the execution path as short as possible: no unnecessary checks after the assignment, and no decisions in the client that the coordinator could make earlier.

Handoff, races, and failure

Every part of the system can fail independently. A scout can die mid-hop, a report can be stale by the time the main account joins, a server can be full, or the boss can be gone when the main account arrives. The coordinator treats all of these as normal.

The handoff is where most of the races live. Two scouts can report the same boss, a scout can reconnect and report again, or the main account can fail to join and need another target. The coordinator owns the assignment state so the clients do not have to coordinate with each other. If a run fails, the target is released and the next valid report can be used. The fleet is designed around retries, not perfect execution.

Grafana as the control room

I did not want to babysit ten clients. The coordinator's metrics land in Grafana: bosses found per account, kills per hour, and the main account's join rate. The per-account counts show which scouts are actually finding things; kills per hour shows whether finds are turning into loot.

The join-rate panel is the quickest health check. If the main account is joining servers, the dispatch path is working. If it flatlines while scouts are still reporting, something between the coordinator and the main account is broken. That is the difference between having a fleet and being able to operate one.

05 · Problems

Hard problems & trade-offs

Three problems took most of the time.

Reverse engineering. The memory value was the foundation and the weakest point. It had to be found, validated, and re-found when the game changed. A false positive was worse than no report, because it sent the main account on a wasted run.

Handoff races. Multiple scouts could report the same spawn, or a scout could report a boss that was already dead. Without a single owner for the assignment state, the main account could be sent twice or sent to a server that no longer had anything. The fix was to keep every decision in the coordinator and make the clients report-only.

Fleet reliability. Ten clients means ten ways to fail: crashes, disconnects, stuck joins, full servers. The scouts had to reconnect and keep moving without manual intervention. The coordinator had to treat a missing scout as normal and keep working with the ones that were still alive.

06 · Results

Results & impact

The result was not a huge number; it was a system that ran without me. The fleet watched ten servers at once, the coordinator handled the dispatch, and the main account only joined when there was a live report. I could check Grafana instead of sitting in the game and hopping servers.

The more useful outcome was operational: I could see which accounts were finding bosses and whether the main account was still joining servers. That made the fleet something I could leave running, not a script I had to watch.

07 · Reflection

What I would do differently

Reverse engineering is a maintenance problem, not a one-time trick. The offset can move, and the detection code has to be easy to re-point. If I rebuilt this, I would put the memory layer behind a cleaner interface from day one.

Distributed state is mostly failure handling. With ten clients, the happy path is easy; the work is in stale reports, failed joins, and duplicate dispatches. A simple coordinator was enough at this scale, but a larger fleet would need a real queue and idempotent assignments so retries cannot double-execute.

Observability is what made it operable. The Grafana panels were not decoration; they were how I knew whether the fleet was working. I would add them earlier next time.

08 · Expertise

Expertise demonstrated

09 · Evidence

Evidence & links

The panel below is the main-account join-rate view from the Grafana dashboard. The rest of the dashboard tracked bosses found per scout and kills per hour.

Grafana panel titled Main Account JoinRate showing the main account's join rate over time
Grafana panel for the main account's join rate. The coordinator only dispatches the main account when a scout reports a live spawn, so this panel doubles as a health check for the dispatch path. Open full size.