Creatures, spells, artifacts, factions, heroes, even the seven basic resources — six different config-loading systems that turn out to be one template, instantiated six times. This is that template, and the mod loader that feeds it strings it has to resolve to numbers it doesn't have yet.
lib/IHandlerBase.h — not inside lib/entities/ at all, but the file everything here is built on — declares one template that every entity handler in the game instantiates.
CHandlerBase<ObjectID, ObjectBase, Object, ServiceBase> supplies everything generic — the objects vector, getById()/getByIndex()/getByName(), forEach(), both operator[] overloads. Six real instantiations confirmed directly from source: CCreatureHandler : CHandlerBase<CreatureID, Creature, CCreature, CreatureService>, CSpellHandler : CHandlerBase<SpellID, spells::Spell, CSpell, spells::Service>, and four more with the identical shape for artifacts, factions, heroes, and resources. A concrete handler's entire job is one override, loadFromJson() — turn one JSON object into one shared_ptr<Object> — plus whatever domain-specific extras it wants on top.The Systems Map's own entities/ row underclaims this directory. It's not a flat pile of 36 files — it's four subdirectories, and it doesn't hold every handler.
entities/artifact/ is the largest and busiest (16 files) because artifacts have real runtime state beyond config — CArtifactInstance, CArtifactSet, CArtifactFittingSet for combination artifacts, alongside CArtHandler itself. entities/building/ is nearly empty (3 files, mostly data, not code) because a building's behavior lives in generic bonus-granting config, not a bespoke class per building. entities/faction/ and entities/hero/ each pair their handler with the object classes it produces (CFaction+CTownHandler; CHero/CHeroClass+CHeroHandler/CHeroClassHandler).
What's not here: CCreatureHandler lives at the top of lib/, and CSpellHandler lives in lib/spells/ — this atlas's Spell System doc already named it without saying what it was built on. Placement here is historical, not architectural — the pattern is what's consistent, not the folder.
One correction to this atlas's own Tooling Belt doc: it calledScriptHandler“a config-loading handler exactly like every other entity handler this atlas has met.” Checked directly —ScriptHandler final : public IHandlerBase, public ScriptService. It shares the mod-facing half of the pattern (loadObject(),registerObject(), the whole point of this section) but notCHandlerBaseitself; it manages Lua script registration through its own storage, not the genericobjectsvector. Close, not identical — worth the correction now that the actual template is on the table.
A creature's JSON can reference a spell that hasn't loaded yet. CIdentifierStorage exists because “load everything in the right order” isn't a promise this system makes.
Every string identifier follows one format, per the class's own header comment: <type>.<name>, camelCase, e.g. creature.grandElf, optionally prefixed with a mod scope (core:creature.grandElf). Instead of resolving a reference the instant it's read, requestIdentifier(scope, type, name, callback) stores a std::function<void(si32)> and moves on — the JSON parser never blocks waiting for something that might not exist yet. Resolution happens later, during a dedicated FINALIZING phase (ELoadingState tracks LOADING → FINALIZING → FINISHED) once every mod has finished registering its own objects.
finalize() is where every scheduled callback actually runs. Anything that still can't resolve is collected into failedRequests — unless it was marked optional — and getModsWithFailedRequests() hands back exactly which mods have dangling references, by name, for the error a modder actually sees. The forward-reference problem doesn't get solved by ordering; it gets solved by deferring the question until every answer that's going to exist, exists.
Between a mod's mod.json saying it adds creatures and CCreatureHandler::loadObject() actually running sits one more layer — and it's also where one mod gets to edit another's data.
one data type, one handler, every mod's contribution
Wraps a single IHandlerBase * for one content type ("creature", "spell", …). Its modData map holds each mod's own new objects, and separately, ModInfo::patches — map<object name, vector<JsonNode>> — every other mod's edits to this mod's objects, keyed by which object they touch.
the map that ties a type name to its handler
map<std::string, ContentTypeHandler>, one entry per content type, indexed by the exact string a mod's JSON uses. preloadData() then load() walk every mod in load order; a patch is just another JSON blob merged in before loadFromJson() ever sees the final object.
This atlas's Bytes and Types doc found one compatibility check — a bounded serialization-version window. This is a second, independent one, layered on top.
ActiveModsInSaveList::serialize() branches on h.saving like any other serializable class this atlas has met, but what it does on each side is asymmetric. Saving: collect every gameplay-affecting active mod and write each one's ModVerificationInfo alongside it. Loading: read that same list back, then call verifyActiveMods() — which, per its own declaration, “throws on failure.” A save file doesn't just remember what version of VCMI wrote it; it remembers exactly which mods were active and checks the running game against that list before anything else happens. Two separate compatibility gates, one for the wire format, one for the content that format describes — neither substitutes for the other.