EVERMIND · PROGRESS DEBRIEF

Field Notes

The long-form answer to "so how's the WoW server going?" — what's running, how the mechanics actually work, and what broke along the way.

TL;DR

01

The Rack

Nothing exotic — the whole thing runs on hand-me-down hardware across a small Proxmox setup. The trick isn't the gear, it's giving each box exactly one job it's actually good at:

laptop node · LXC 200

THE GAME SERVER

An old laptop running Proxmox. One Linux container gets 13 of its 15GB of RAM — not because the server needs it to compute, but so the map data stays in page cache. When that cache starves, the game stutters. RAM as a moat.

gaming desktop · RTX 3060

THE VOICE

The Windows gaming PC doubles as the AI box: Ollama serving a 12B model (unslopnemo-rp:12b, 9.9GB pinned in VRAM at 16k context). Every line of companion dialogue is a network call from the container to this GPU.

nuc1 · support LXCs

EVERYTHING ELSE

A NUC carrying the boring-but-vital tier: vector database for bot memories, Grafana for dashboards, and the Proxmox Backup Server the game node is being joined to.

OMV box · 4TB spinning

COLD STORAGE

Old Optiplex with a big slow disk. Cold archives and SQL dumps only — an earlier outage proved the hard way that hot state behind spinning NFS is how you take down fifteen containers at once.

02

The Stack, Top to Bottom

03

The Big Idea: a Ledger Between Two Worlds

The core problem: the game engine knows everything that happens — every kill, death, and wipe — but writes none of it anywhere a script can read. Seven planned features were stuck on exactly that. The fix was one pipe, built once: a Lua script inside the engine writes interesting events into a database table, and the Python sidecar polls that table and acts on them. Every mechanic below is just a handler plugged into that loop.

Why a plain table instead of a proper queue? Because it's inspectable with a SELECT, replayable after an outage, and survives either side restarting. The consumer claims events and marks them consumed in the same transaction, so a crashing handler can't wedge the feed — one bad row gets logged and skipped, and the batch continues.

The dangerous part was volume. 650 bots fight constantly; logging every kill would mean thousands of rows a minute into the database whose responsiveness is the game — and disk thrash is the documented way this server dies. So the Lua side filters before writing: kills only count if the victim was rare/elite/boss or a human was nearby, deaths only near a real player, logins only for real players. Baseline result: 4 rows in 5 minutes with nobody logged in.

The failsafe mindset: there's also a circuit breaker — if more than 400 events land in one minute, the script stops writing and complains loudly. If a filter ever breaks, the failure mode is a noisy gap in the data instead of a silently thrashed database. Every risky system in the project follows that pattern: fail loud, fail safe.
04

The Mechanics, and How Each One Works

Bounty board

A rare, elite, or boss dies anywhere in the world → the realm posts a bounty on it. Whoever lands the next kill collects.

HOW · A handler on the event feed inserts a bounty row; a database unique constraint enforces one open bounty per creature, so a rare that dies on respawn forever can't flood the board. First live test: a bot killed a rare, the filter judged it notable, the bounty posted — the whole chain worked on the first real kill.

Nemesis grudges

A bot that kills you starts a grudge and climbs a rank ladder with each repeat: Rival → Menace → Scourge → Nemesis → Archnemesis.

HOW · One database row per killer-victim pair, not per bot — so a bot can be your Archnemesis and a stranger to everyone else. That single schema choice is what makes it feel personal. PvP deaths always pass the volume filter, because they're the signal this feeds on.

Wipe detection → incident reports

A party wipe raises an "incident" recording who died, where, and to what — the plan is to mail it to you as a snarky root-cause analysis.

HOW · A wipe isn't a single event — it's "several deaths, same place, within 30 seconds." The Lua script stays dumb and cheap (one death = one row); the Python side correlates across events, because holding cross-event state inside the game process would blow the performance budget. Tested both ways: three quick deaths raise an incident, the same three spread over minutes correctly don't.

2,785 permanent personalities

Every character on the realm has a fixed identity — archetype, voice, temperament, motivation, one oddly specific habit.

HOW · No LLM involved. Identity is a pure function of the bot's GUID, name, race, and class — a deterministic hash into trait tables. Same bot, same person, forever, at zero token cost; re-deriving 400 of them produced zero drift. An LLM would've been slower, cost money, and — the real killer — invented a different person on every run. All 168 archetype×voice combos get used. The registry has since grown from 1,154 to 2,785 identities, and the compiled personality cards — 2,798 of them — are now deployed to the whole population, the switch an earlier version of this page called "staged."

Companions that don't hallucinate lore

Party companions chat freely, but factual questions — "where should we quest?" — get answers grounded in the game's real data, or an honest "I don't know."

HOW · A knowledge service sits between the model and the answer: it returns actual rows from the world database (with quest IDs), checks level ranges and the race-restriction bitmask, and refuses to fabricate when it has nothing. The LLM only chooses the wording. Tested with real characters on both factions — no invented zones, no Tauren sent to Stormwind.

Rumors, beliefs, and secrets

Bots hold beliefs about events — possibly second-hand, possibly wrong — and gossip spreads with a paper trail.

HOW · Facts and beliefs are separate tables: what happened vs. what each character thinks happened. Relationships are directional (A can like B while B loathes A). Rumor confidence multiplies by 0.6 per retelling, so gossip dies out after ~6 hops instead of circulating forever. Privacy is tested adversarially — the suite actively tries to extract one character's secrets through another and asserts it comes back empty.

Street conversations

Characters near you chat with each other, unprompted, about where they actually are — cities feel inhabited instead of staffed.

HOW · The previous attempt fired ambient chatter randomly across two continents, so with the client's 40-yard say-range, you never saw any of it. The director now only casts bots within those 40 yards of a player, on cooldowns (90s per player, 10min per bot, hourly cap), with 45-second quiet windows during combat and quest text. Cheaper and more visible. First unprompted exchange happened two minutes after arming — two bots discussing shelter from the live storm, by name.

Bot society — the newest layer

Bots now form their own parties, remember who they've grouped with, and can found their own guilds. The 20 premade factory guilds were deleted to make room for real ones.

HOW · The best discovery of the project so far: a whole cluster of bot social behavior — grouping, guilds, PvP — ships inside the playerbots module but is commented out upstream, reachable through one config string. Re-enabling grouping took party formation from one group in thirteen hours to ten in twenty minutes. A sidecar job snapshots memberships every ten minutes into "bands," so a recurring lineup becomes a recognized group with history. The roadmap here borrows from Project Sid — the 1,000-agent Minecraft experiment where roles, laws, and culture emerged — as the measuring stick for what a 650-bot society can produce.

The undo button

Cities can be snapshotted and restored — the prerequisite for ever letting AI events damage a town.

HOW · snapshot records every creature spawn in a hub while the world is pristine (Orgrimmar: 1,243 · Stormwind: 1,288); restore re-adds whatever's missing. It only ever adds — running it twice is harmless — and it refuses to overwrite a baseline without --force, because re-snapshotting a damaged world would destroy the thing you'd restore from. Proven by actually deleting spawns and getting them back byte-identical, on a scratch copy of the table so the test itself couldn't hurt the live game.

05

Numbers That Decided the Design

A few measurements ended up steering the whole architecture — measured, not guessed:

NUMBERWHAT IT MEANS
2.4 lines/secThe GPU's dialogue ceiling. Two concurrent LLM calls are free (same latency, double throughput); past two, latency climbs linearly for nothing. So ambient chatter gets one slot, and the second stays reserved for the player — that one benchmark dictated the entire conversation-scheduling design. Re-measured after the model swap: the 12B holds 2.46, no slower than the 8B it replaced.
13h → 20minTime for the bots to form one party before and after re-enabling the grouping strategy the module ships disabled. The feature existed all along — it was one config string away.
70–140 botsHow many characters can plausibly hold conversations within that ceiling — out of 650. The rest stay silent by design, not neglect.
40 yardsThe client's say-range. Any social feature that fires outside it is invisible theater — this number is why the old city-chatter never appeared and the new one does.
4 rows / 5 minEvent-feed baseline with 650 bots grinding and nobody logged in. The filters work. The remaining gate: under 500 rows in 10 minutes with a human playing.
96.7%Share of creature rows with zoneId = 0 — see the scars below for why that number nearly sank the restore system.
0.6× per hopRumor confidence decay. After six retellings a rumor is at 4.7% and effectively dead — gossip with a half-life.
06

Scars — What Bit Me, So It Doesn't Bite You

07

What's Next

That's the honest state of it: the spectacular stuff is still ahead, but the boring load-bearing layer underneath — the event feed, the consumer, the failsafes, the tests — is built, measured, and running. Which, as any homelabber knows, is the part nobody sees and the part everything depends on.

Questions welcome.

— moon

Evermind is an unofficial, private, non-commercial fan project and is not affiliated with or endorsed by Blizzard Entertainment. World of Warcraft is a trademark of Blizzard Entertainment, Inc.