Template, Instance, Runtime

How an authored YAML room or mob becomes a live world object: a deliberately incomplete save overlay, and the restore pass that puts authored intent back on top

Template, Instance, Runtime How an authored YAML room or mob becomes a live world object: a deliberately incomplete save overlay, and the restore pass that puts authored intent back on top read the authored file for this id LoadRoomInstance opens by calling LoadRoomTemplate with no condition on it at all. There is no path where a saved file is read and the authored file is not. The world is authored in YAML first and everything else is a modifier on top of that. unmarshal into a fresh struct Title, description, exits, nouns, hidden nouns, biome, coordinates, spawn lists, idle messages, crafting station: the authored content of the room. For a mob the same step brings in stats, behaviors, shop stock, dialogue id, schedule id and patrol id. template values only, nothing saved yet does a saved instance exist for this room? A plain os.ReadFile. Most rooms in the world have never been written to rooms.instances/ and never will be, so for most of the world the whole overlay layer is skipped and the diagram ends four steps early. if present: unmarshal ON TOP of the same struct yaml.Unmarshal(bytes, room) reuses the struct that already holds the template. Every field named in the save replaces the template value silently, with no log line and no diff. This is exactly the mechanism behind the recurring complaint that a content edit was not taking effect. does a saved instance exist for this mob? Keyed by mob id, zone, name and home room, so the same template spawned in two rooms keeps two independent histories. Charmed companions and dispatched bounty hunters are excluded by design and never get a file. if present: copy training, skills, mutations, gold, gear The mob overlay is a different shape from the room overlay. It is not a raw unmarshal onto the struct, it is a small fixed record type, MobInstanceData, holding only progression and planner state. Anything absent from that record simply cannot be carried by a mob save. read the authored file a second time The overlay has just finished, so the struct is a mixture of authored and saved values with no way to tell them apart. The only reliable source of authored truth is the file itself, read again from disk. a clean, unmodified copy copy back every field tagged instance:"skip" restoreSkipTaggedFields reflects over the Room type and re-sets 23 exported fields from the clean template: RoomId, Zone, Title, Description, Exits, Nouns, HiddenNouns, SpawnInfo, IdleMessages, Biome, MapSymbol, MapLegend, X, Y, Z, Plane, Pvp, Station, MusicFile, IsBank, IsStorage, StorageCapacity, IsCharacterRoom. The yaml library cannot see the instance tag, so this pass exists to enforce it after the fact. re apply DefusedExits, because the restore brought the armed traps back Exits are skip tagged, so the pass above just resurrected any lock trap a player had disarmed. DefusedExits is deliberately not skip tagged, so it survived the overlay as a plain list of exit names, and applyDefusedExits puts the disarm back. A small hand made exception to a blanket rule. enter the in memory cache addRoomToMemory. Every later LoadRoom for this id returns this same pointer without touching either file, so from this moment the in memory object is the source of truth for instance state and the files are only its backing store. one object per room id for the process lifetime save what changed On shutdown, on a periodic sweep, or when a template is rewritten by the web builder. Ephemeral rooms are refused before anything else happens: a room id at or above the ephemeral floor returns an error on the first line of SaveRoomInstance. load the template again, to diff against it SaveRoomInstance reads the authored file a third time in this story. It needs a reference copy so it can ask, field by field, whether the live value actually differs from what an author wrote. write only fields that differ and are not skip tagged Four filters in order: unexported fields, fields tagged yaml "-", fields tagged instance "skip", then fields that DeepEqual the template. What survives is items on the floor, gold, containers, stash, signs, mutators, defused exits and the long term data store. If nothing survives, the file is deleted rather than written empty. write only the fields the record type declares The mob side reaches the same guarantee from the opposite direction. The room writer is a deny list, a rule that removes fields from a struct that contains everything. The mob writer is an allow list, a separate record type that was only ever given the fields a mob is permitted to remember. a stale save cannot shadow the skip-tagged fields That is the whole point of the asymmetry. The overlay is powerful enough to remember a dropped sword and a spent shop, and narrow enough that rewriting a room description or adding an exit takes effect on the next load with no file to clear first. Template first, always The overlay, only if a save exists Copying skip-tagged fields back The live object What gets written back Loader · LoadRoom · NewMobById · Sequence participant Loader LoadRoom · NewMobById Template YAML · rooms/ · mobs/ · Sequence participant Template YAML rooms/ · mobs/ The struct · one Room or Mob value · Sequence participant The struct one Room or Mob value Room save · rooms.instances/ · Sequence participant Room save rooms.instances/ Mob save · mobs.instances/ · Sequence participant Mob save mobs.instances/ Skip restore · copies 23 fields back · Sequence participant Skip restore copies 23 fields back Live object · in memory, one per id · Sequence participant Live object in memory, one per id Save writer · deny list, allow list · Sequence participant Save writer deny list, allow list Legend request return security async trace default message

Three files, one struct

  • • The authored template under rooms/ or mobs/ is read first, every time
  • • Templates: _datafiles/world/dogmud/rooms/<zone>/<id>.yaml and mobs/<zone>/<id>-<name>.yaml
  • • Room saves: rooms.instances/<zone>/<id>.yaml
  • • Mob saves: mobs.instances/<zone>/<id>-<name>-room<n>.yaml, so one template spawned in two rooms keeps two files
  • • A saved instance under rooms.instances/ or mobs.instances/ may not exist
  • • Most rooms in the world never acquire an instance file at all
  • • Once loaded, the in memory object outranks both files until the next restart

Two overlays, two mechanisms

  • • A room save unmarshals directly onto the struct, so any field it names wins
  • • A mob save is a fixed record: training, skills, mutations, gold, gear, plan state
  • • The room overlay is limited by a tag; the mob overlay is limited by its type
  • • Charmed companions and bounty hunters are excluded and never write a file

What a stale save cannot override

  • • 23 Room fields carry instance:skip and are copied back from a clean template
  • • Title, description, exits, nouns, biome, coordinates and spawn lists are among them
  • • The yaml library cannot see the tag, so the restore pass enforces it afterwards
  • • Spawn lists are in this group: a spawn edit needs no instance wipe to take effect
  • • DefusedExits is deliberately excluded, and is re applied after the restore

What actually gets written

  • • SaveRoomInstance is the deny list, SaveMobInstance is the allow list
  • • Unexported fields, skip tagged fields, and fields equal to the template are dropped
  • • What remains is floor items, gold, containers, stash, signs, mutators, room data
  • • An instance file with nothing left in it is deleted instead of written
  • • Ephemeral rooms refuse to save: their id sits at or above the ephemeral floor

Traced from source

  • • github.com/pruuk/DOGMud at d383a6fffbf24b9ed455e47d503567d0ff35cf42
  • • internal/rooms/save_and_load.go, internal/rooms/rooms.go
  • • internal/mobs/instance_save.go, internal/mobs/mobs.go