In 2005 I played a hunter who would not group with anyone who typed in all caps. I never learned his name. I remember the rule.
That is the thing worth building here. Not a bot that clears a dungeon - plenty of those exist. A population. A server where the other characters have habits, prices, grudges, and schedules, and where a human logging in at two in the morning can find five of them willing to run Wailing Caverns.
Westworld of Warcraft is that server. This series is how it is built and why each part is shaped the way it is.
Why One Bot Was Never the Point
“Make bots that play WoW” is a solved and boring problem. “Make a server that behaves like it has a thousand people on it” is neither.
The gap between those two sentences is where all the engineering lives:
- One bot grinding boars is a state machine. A thousand bots grinding boars is a scheduling and economy problem.
- One bot pathing to a vendor is A*. A thousand bots pathing across Azeroth is a mesh generation and caching problem.
- One bot casting Fireball is a rotation. A thousand bots casting Fireball with identical timing is a detectable signal that no human population produces.
- One bot answering a whisper is a template. A thousand bots answering whispers is a rate-limiting and anti-griefing problem.
I keep one north star for the whole project: a new human player logging in cannot tell, from gameplay observation alone, that the population is machine-controlled.
What “Alive” Actually Means
Vague goals produce vague systems, so before writing anything I made myself define “alive” as four testable properties:
- Always-available activities. Any legal activity - questing zone, dungeon, raid, battleground, profession route, world event, world boss - has participants available inside the activity’s normal group-form window. A human request for a level-appropriate dungeon group resolves in under five minutes.
- Continuous progression. Bots not serving a human request advance toward their own roster goals: level, gear, attunement, reputation, profession, gold, mount, PvP rank. An idle bot is a bug.
- A living economy. The auction house shows posting and bidding activity around the clock. Vendors see traffic. Banks see deposits. Mail moves. The economy reaches steady state without seeding.
- Operator clarity. The console shows me the highest-volume errors, the active activities, the population, and the scaling pressure points. Choosing my next engineering task is a query, not a guess.
Notice what is not on that list. There is no requirement that bots fake incompetence - misspelled trade chat, aimless wandering, theater. Social texture itself is absolutely in scope (the storyline and social-fabric posts depend on it); what is out of scope is performing incompetence to seem human. Indistinguishability here is a property of timing and routing.
Three Thousand Characters, On Purpose
The target population is 3,000 characters, and I picked its distribution deliberately.
Those 3,000 are not all logged in at once. Every character carries its own online window, so a typical hour has roughly a thousand of them in the world. Where this series does per-hour arithmetic - trade posts, mail volume, chat budgets - it runs at a thousand bots and means that number. The 3,000 is the roster; the thousand is the crowd.
| Dimension | Target |
|---|---|
| Faction split | Roughly 50/50, configurable per realm |
| Class and spec coverage | Every race, class, and spec combination present at every five-level bracket, weighted toward level 60 |
| Professions | Every primary profession represented at 300 skill; cooking, first aid, and fishing universal |
| PvP | Enough queue depth for Warsong Gulch at 10v10, Arathi Basin at 15v15, and Alterac Valley at 40v40 inside bracket boundaries |
| Raids | Enough attuned level 60s for one concurrent raid in each tier: Onyxia, Molten Core, Blackwing Lair, Zul’Gurub, AQ20, AQ40, Naxxramas |
A RosterPlanner owns account-level decisions and enforces the coverage rules, in order. The counts below are the vanilla 1.12.1 roster; the later clients add a class and professions to both.
- Faction bootstrap. If the plan needs a shaman and the account has no Horde characters, a Horde character gets created first.
- Class coverage. All nine classes reach 60 before any class is duplicated at 60.
- Profession coverage. All nine primary professions distributed; none left unrepresented at 300.
- Spec diversity. Each class fields at least one of each role it can fill.
- PvP rank. The roster holds characters at each rank band Alterac Valley objectives need.
This is why the server does not end up as 3,000 fury warriors. Somebody has to be the enchanter.
What Is Actually Running
The stack reads from the bottom up. Exports/GameData.Core holds the game interfaces and shared contracts with zero dependencies - it is the bottom of the stack on purpose, because everything else stands on it. Exports/BotCommLayer carries protobuf over TCP with length framing, along with the .proto sources and their generated C#. Exports/BotRunner is the behavior engine itself: task stack, objective decomposition, shared by both runtimes. Exports/WoWSharpClient is a pure C# implementation of the WoW protocol - packets, opcodes, auth, movement. Exports/Navigation wraps C++ Detour pathfinding plus the PhysicsEngine.dll collision target, and Exports/Loader with Exports/FastCall provide the C++ CLR-injection bootstrap and structured-exception-wrapped fast calls.
The service tier does the coordinating. Services/WoWStateManager is the orchestrator: bot lifecycle, foreground injection, IPC listeners, activity registry, legality. Services/PathfindingService runs A* routes over the native navigation layer, and Services/SceneDataService feeds collision and scene geometry to background bots. Services/DecisionEngineService produces advisory recommendations for objectives, rewards, rotations, threat, chat, and personality; Services/PromptHandlingService hosts the persona and dialogue runtime plus the storyline graph store. BotProfiles carries the per class and spec combat rotations.
On top sit the loopback-only Blazor Server consoles, UI/OperatorConsole and UI/StorylineManager, on ports 5167 and 5157, with the storyline runtime API alongside them on 5147, and UI/Systems/Systems.AppHost, which does the .NET Aspire orchestration of the Docker stack and the services.
The dependency direction is strict, and a test enforces it:
GameData.Core -> BotCommLayer -> BotRunner -> WoWSharpClient -> Services -> UI
Interfaces live in the lower layers. Implementations live in the higher ones. An Exports/ project may never reference Services/, UI/, or Tests/. When someone tries, ProjectLayeringTests fails the build.
Two Ways to Drive a Character
There are exactly two runtimes, they share the behavior engine and the game interfaces, and neither is a fallback for the other.
The foreground runtime injects a native loader into a real WoW.exe, bootstraps the .NET runtime in-process, and drives the character through direct memory reads and writes plus Lua. You reach for it when you need true client parity: rendering, exact physics, packet captures that serve as parity baselines.
The background runtime is headless. A pure C# implementation of the WoW protocol connects to the world server with no game client at all, which is how you get many bots cheaply, or CI without a GUI.
Both are real, both ship. The next post takes them apart.
Why a Legacy Private Server
This question comes up immediately and it deserves a direct answer. The supported clients are Vanilla 1.12.1, Burning Crusade 2.4.3, and Wrath of the Lich King 3.3.5a, running against a locally hosted MaNGOS-family world server. Modern retail WoW is not supported and is not a goal.
Four reasons, in roughly this order.
Consent. Everyone on the server is either the operator or something the operator started. Nobody’s competitive experience is being degraded.
Stability. 1.12.1 is a fixed target. Memory offsets, opcodes, and physics constants do not move under you between patches.
Observability. The operator owns the world database, the server logs, and the SOAP command interface. You can ask the world questions directly.
Research value. The interesting problems - coordination, economy, planning, indistinguishability - do not require the newest client to be interesting.
The stated purpose is intellectual exploration. That is a stronger position when the environment is one you own outright.
The Invariants
These survive every refactor. Breaking one is a priority-zero bug, and most of the rest of this series is downstream of them.
| Invariant | Why |
|---|---|
| No blind sequences | Counters, sleeps, and fixed repeat-N-times loops are banned for state validation. Gate on memory, packet, snapshot, or explicit API state. A bot that waits three seconds and hopes is not a bot, it is a superstition. |
| Foreground is ground truth | When the foreground and background runtimes disagree about physics or movement, the foreground is right and the background is wrong. |
| StateManager owns orchestration | Tests, the UI, and external callers never bypass it to talk to a bot directly. |
| Geometry has exactly two owners | The pathfinding and scene-data services answer every world-geometry question. Bot code does not load map tiles. |
| Tests assert through snapshots | A test that reaches into internal bot state instead of reading a published snapshot is testing the wrong thing. |
| No skipping for “resource not found” | If a fishing pool exists in the world database and the bot cannot find it, that is a detection or pathfinding bug, not a reason to skip the test. Walk further. |
| The catalog drives legality | Every rejection of an illegal activity cites a specific catalog field. No ad-hoc legality logic inside the behavior engine. |
The one that changes the most code is the first. “No blind sequences” is why every task in the system carries a verification predicate, and why the behavior hierarchy in Activity, Objective, Task, Action looks the way it does.
Where This Series Goes
Four groups. Foundations covers how a character gets driven, how behavior is structured, and how movement is made correct. Population covers where personality comes from, who the standing cast is, how they talk, and how the economy forms. World and intelligence covers server-wide time, the boundary around machine learning and language models, and how a human asks the world for something. Proof covers how you test a world, and the synthesized reference architecture.
The population posts are the ones I would read first if I were you. Architecture is the substrate; the cast is the point.
The Idle Bot
Every failure worth designing against on this project is loud except one, and the quiet one is the idle bot.
- A level 34 rogue completes its zone quest chain in Stranglethorn Vale.
- The progression planner has no next objective because the bracket’s catalog rows are incomplete.
- The bot stands in Booty Bay.
- Nothing errors. Nothing alerts. The dashboard is green.
That is worse than a crash. A crash tells you where it hurts; a standing rogue tells you nothing until a human wanders past and sees a statue. That is why “an idle bot is a bug” sits in the definition of alive at the top of this post, and why the operator console surfaces population activity distribution as a first-class panel.
How I Will Know It Worked
Most of the finish line is unglamorous, and I expect to hit it quietly. Every activity in the catalog gets an automated test that drives a request through to a real group and a real completion. All 27 class and spec combat profiles pass live validation in both runtimes. The operator console renders population, active activities, top errors, and queue depth, and picks up config changes without a restart. Reproducible client crashes get a hardening fix or a written mitigation. None of that is interesting once it passes; it is only interesting while it fails.
Three of the criteria carry an actual argument. The staged load run has to reach 3,000 concurrent bots with snapshot latency holding under half a second at the ninety-ninth percentile, because the population is the product and a design that only works at fifty bots is a different design. Normal-operation logs have to be quiet enough that any warning is signal, because the idle bot above is invisible inside a noisy log. And every pattern that landed has to be written down as a technique someone could reuse, with an eye toward whether it transfers past this one game. That last one matters more than it looks. If none of this transfers to another game, then what got built is a WoW bot, not a method.
Related Posts
Start here, then read Two Ways to Wear a Character for the runtimes and Activity, Objective, Task, Action for the behavior model. The project page carries the original 2018 goals and how far they have moved.