Every town, dwelling, Pandora's Box, and resource pile a hero can step on is a CGObjectInstance subclass built by a two-tier factory this atlas's own Entities doc doesn't cover — and visited through one virtual call that can turn into a network round trip before it returns.
The split at the top level is the same split this atlas has seen before — the object and the thing that builds it live in different places.
lib/mapObjects/ holds the instances: CGObjectInstance is the base every placed object shares, and concrete subclasses — CGTownInstance, CGHeroInstance, CGCreature, CGDwelling, CGPandoraBox, CGResource, CGMarket — carry whatever state that specific kind of object needs. lib/mapObjectConstructors/ holds the factories that build them: CObjectClassesHandler at the top, a family of per-type constructors (DwellingInstanceConstructor, CRewardableConstructor, MarketInstanceConstructor, ShipyardInstanceConstructor, and more) underneath it.
This atlas's One Handler, Every Entity doc found one generic template, CHandlerBase<>, behind creatures, spells, and artifacts. Map objects don't use it — confirmed directly: class CObjectClassesHandler : public IHandlerBase, not CHandlerBase<>. What it uses instead matches how HoMM3 itself always modeled objects: two keys, not one.
CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObjectSubID subtype) is the two-key lookup, and handlerConstructors is a map<string, function<TObjectTypeHandler()>> keyed by handler name ("dwelling", "rewardable", "market"…) rather than one template parameterized per entity type. AObjectTypeHandler::create() only instantiates the right subclass and stamps its ID — it has to work at map-load time, before a game session or an RNG exists. configureObject() is a separate, later call that does the rest, and its own comment states it “should be re-entrable, resetting state of the object if necessarily.”A hero stepping onto an object's tile is one virtual call. What happens after that call depends entirely on which object it was.
IObjectInterface is the runtime behavior every placed object implements — onHeroVisit(), onHeroLeave(), newTurn(), plus a family of resume-points for things that can't finish in one call: blockingDialogAnswered(), battleFinished(), garrisonDialogClosed(), heroLevelUpDone(). CGameHandler calls the first one directly — visitedObject->onHeroVisit(*this, h) — with no idea which subclass it's actually calling into.
When the object needs an answer from the player — which reward to take, whether to fight a garrison — the visit doesn't just return. It opens a VisitQuery (a CQuery subclass, the same query mechanism this atlas's Wire Protocol doc traced for battles) that blocks further packs from that hero's owner until it's resolved. The player's answer arrives as its own pack, and the server calls back into the exact same object through blockingDialogAnswered() to finish what onHeroVisit() started. A Pandora's Box asking “which reward?” and a battle asking “you won, continue?” are the same mechanism, just parked on different objects.
Pandora's Box, a Seer's Hut, a resource pile, and some dwellings look nothing alike. They share one base underneath.
class CRewardableObject : public CArmedInstance, public Rewardable::Interface — the second base is the shared machinery, in its own directory, lib/rewardable/. Rewardable::Interface::grantReward() is pure virtual and does the actual granting; selectRewardWithMessage() is what turns a list of candidate rewards into the dialog a player answers, and grantRewardBeforeLevelup()/grantRewardAfterLevelup() exist specifically because a reward can include experience that triggers a hero level-up mid-grant — another resume-point, same shape as heroLevelUpDone() above. Every concrete reward object overrides onHeroVisit() and blockingDialogAnswered() the same two ways; what differs between a Pandora's Box and a Seer's Hut is only the reward list a mod author configured, not the mechanism that offers or grants it.
This atlas's own Random Map Generator doc found TreasurePlacer placing objects by value, without saying where that value lives. It's right here.
AObjectTypeHandler carries its own RandomMapInfo rmgInfo member — four fields, all real: value (1k worthless, 10k Utopia-level, per the struct's own comment), mapLimit (an unset optional means no cap; zero means the RMG can never place this type at all), zoneLimit, and rarity. Every object type a mod adds carries its own placement weight without the generator needing to know anything object-specific — getRMGInfo() is the one call that connects them.
One more concern worth naming separately: ObjectTemplate is appearance and geometry — the sprite, which tiles are blocked, which tile is visitable — not behavior. A mod can give an object a new look without touching what onHeroVisit() does, and vice versa; the two are deliberately unrelated classes.