VCMI · Heroes of Might & Magic III engine
Systems Map
A top-level survey of how the engine is built: one shared library, a server that alone may change game state, a client and a set of AIs that read a local copy of it. Use this to pick where to dig in next.
Core Library ·
Server ·
Client ·
AI ·
Tooling & Apps ·
Threading ·
Index
The one rule the whole engine is built on
Everything above — UI, AI planning, replay, the map editor — is downstream of a single asymmetry: state has exactly one writer.
CLIENT · AI
same interfaces serve both
local cached state
client & AI read here
read via Callback · no network hop
SERVER
sole writer of state
CGameState
written only here
validated before every write
request · network pack
result · broadcast pack
Game state has one writer. Client and AI act by sending a request across this boundary; the server validates it and broadcasts the result. Everything else — rendering, AI planning, the UI — reads a local cache that the broadcast keeps in sync, without touching the network. What's actually inside those two arrows is its own document: the wire protocol →
The regions
Five areas, each built and shipped differently: one static core, three consumers of it, and a belt of standalone tools.
vcmiMain — game rules and data structures, no rendering or transport code of its own
bonuses/28 The bonus system — every stat, resistance and immunity flows through two DAGs of nodes with propagators, limiters, and updaters — dig in →
battle/46 core combat rules, damage calculation, unit state — dig in →
gameState/26 CGameState itself — the authoritative snapshot — dig in →
callback/34 read-only interfaces AI & client query instead of touching state directly — dig in →
entities/36 heroes, creatures, artifacts, spells, buildings — dig in →
mapObjects/48 + mapObjectConstructors/ (24) — towns, dwellings, mines and the rest of the adventure map — dig in →
rewardable/10 the requirement/grant pair behind every reward object — dig in →
rmg/78 random map generator — the largest single subsystem in lib — dig in →
spells/52 spell definitions and casting logic — dig in →
serializer/29 save-game and network serialization framework — dig in →
networkPacks/20 the packet vocabulary client and server speak to each other — dig in →
modding/21 mod and content loading — dig in →
filesystem/, json/, texts/68 archive loading, config parsing, localization
pathfinder/, campaign/28 hero pathfinding — dig in → ; campaign progression — dig in →
vcmiservercommon — the only process allowed to mutate CGameState
CGameHandler the hub — validates every incoming request, is the single write path — dig in →
battles/ server-side combat flow processing — dig in →
queries/ player decision prompts (e.g. “choose a reward”) — dig in →
processors/ turn, hero and economy processing — dig in →
SDL2-based — renders and reads the local cache, never writes state directly
gui/ the CIntObject widget framework everything else builds on — dig in →
render/ + renderSDL/ — rendering abstraction over SDL2
eventsSDL/ keyboard, mouse, touch and gamepad input — dig in →
mapView/ adventure map rendering — dig in →
globalLobby/ vs. lobby/ — online matchmaking UI vs. local game setup — dig in →
statically linked, chosen by name through AIFactory — no dynamic loading
Nullkiller2 modern adventure-map AI — default — dig in →
StupidAI minimal combat AI for neutral / passive players
MMAI experimental, machine-learning-based combat AI
EmptyAI no-op stub, used for testing
Threading model
Four lanes running alongside each other inside the same process pair.
Main GUI input processing and rendering
runNetwork processes incoming packets, runs combat AI reactions
runServer processes requests, applies game state updates
AI tasks TBB-based parallel tasks for adventure-map AI
Index
Every deep dive published from this map so far, in the order they were dug.
First pass — lib/
⚔️ how a fight starts, the turn loop, and a Lua-scripted damage calculator with no C++ fallback
🎲 zone placement, then a dependency-scheduled modifier graph — not a fixed generation order
🔮 one CSpell definition, two unrelated runtimes depending on where it's cast
📜 a loaded map, a generated map, and a saved map all converge on one CMap
🕷️ two DAGs, not one — inheritance is lazy, propagation is eager
📡 the double-dispatch visitor underneath this map's own “one rule” diagram
Second pass
🧠 Nullkiller's depth-first goal decomposer, and the deterministic shadow battle BattleAI fights first
the parent-child tree and the input dispatch lists are two independent structures
🧰 one shipped library, four programs, and the Lua script registry finally explained
Third pass
💾 the one CGameState::apply() that the server and every client run independently against their own state
🌅 where the NewTurn pack is built, and why simultaneous turns are decided by an actual pathfinder
🔢 how a byte stream remembers its real type, and where the canonical serialization doc has drifted from the code
👁️ the read side of this atlas — layered query interfaces, where a request pack is actually built, and a randomizer that remembers your bad luck
🥱 one template instantiated six times, a mod loader that resolves names it doesn't have yet, and a save that remembers exactly which mods wrote it
🧙 a Dijkstra search over layered path nodes, gated by a fixed five-rule chain the player, the turn-order check, and the AI each configure differently
🏰 a two-tier class/subtype factory behind every town and treasure, and a hero visit that can pause on a network round trip before it finishes
🎛️ the real pack-dispatch entry point every other doc cited, and the one mechanic — a hero's single step — it never delegates to a specialist
🖼️ a map renderer that never branches on what it's drawing, and the wait that pauses pack processing until a hero's walk finishes on screen
🎮 the screen's own mode switch flipping alongside the map view's, and a shortcut table that rebuilds its enabled flags from that state every time it's read
🎁 two near-mirror-image structs for what a reward needs and what it gives, and a second, differently-shaped solution to a problem the Bonus System already solved once
🥳 a campaign as a container of embedded, ordinary .h3m files, and the one hero-carrying serialization path this atlas's binary framework was never meant to cover
Status
The first pass through lib/ is done — Battle , the Random Map Generator , Spells , Map Formats , the Bonus System , and the wire protocol underneath the “one rule” diagram above . The second pass ran three stops: Adventure & Battle AI → — Nullkiller's depth-first goal decomposer, and the deterministic shadow battle BattleAI fights before it fights the real one — the widget tree → — built implicitly by a construction-scope stack, read by an entirely separate flat list the moment a click actually happens — and the tooling belt → — four programs, one shipped library, and the Lua script registry three earlier docs had already been calling into. Every row this map named at the start had a document behind it after that. The third pass goes underneath rows the first two passes already touched: the apply layer → — the one CGameState::apply() that the server and every client run independently against their own state, and the replay log built on the assumption that recording the packs is the same thing as recording the game — and the new turn → — where that pack actually comes from, three side effects it never carries, and a full pathfinder run just to decide whether two players can keep acting at once — and bytes and types → — the hand-rolled vtable that lets a CPack* remember it was really a MakeAction, and two features the canonical serialization doc still describes that no longer exist in this codebase — and asking the game → — the read side to all of it, and a morale-and-luck randomizer that biases its own next roll toward fixing a run of bad ones. Fifth stop: one handler, every entity → — the single template every creature, spell, artifact, faction, hero, and resource handler is built from, and the deferred-resolution trick that lets a JSON file reference a name that hasn't loaded yet. Sixth: the rules of the road → — a fixed five-rule chain under a Dijkstra search, reconfigured by PathfinderOptions for a normal move, a simultaneous-turn contact check, and the AI's own extension of the same engine. Seventh: what's on the map → — a class/subtype factory this atlas's single generic handler template doesn't cover, and the exact query mechanism a Pandora's Box shares with a finished battle. Eighth: where requests land → — the actual function every earlier doc's CGameHandler citations pointed at, and why a hero's single step is the one thing it does itself instead of handing off. Ninth: one renderer, many contexts → — the swappable object that lets a single map renderer stay ignorant of whether it's drawing a normal frame or a hero mid-step, and the wait that blocks pack processing until that step finishes on screen. Tenth: seven states, one screen → — the mode switch that flips alongside the map view's own context, the screen that's told about server events rather than asking for them, and a shortcut table that regenerates its own enabled flags on every read. Eleventh: the limiter and the reward → — a row this map never had, added for it: two flat structs mirroring each other's vocabulary, and a second answer to the eligibility-composition problem the Bonus System had already solved a different way. Twelfth: what crosses the border → — a campaign that turns out to be nothing but ordinary .h3m files in a trenchcoat, and the one hero-carrying handoff this atlas's binary serializer was never meant to reach.