Skip to main content
vetra-example-adapter ships in the same archive as the product. It is optional: install it only if you are writing an adapter and want a working one to read.
Read this rather than a built-in adapter. The props adapter is several hundred lines of Source engine detail and almost none of it is the contract. Everything here is.

The domain it integrates

A fictional addon that stores named markers on a map: a navigation waypoint, a shop location, a spawn zone label. That domain was chosen for three properties most real integrations share, and that props and NPCs happen not to have:
  • a marker is not an entity. There is nothing in the world to find;
  • a marker has no model, so it cannot be previewed as one;
  • a marker has its own durable record id, which is what makes honest persistent identity possible.

1. The addon being integrated

Everything in this section is the “third-party addon”. None of it knows what Blueprints is, which is the point: an adapter is a bridge, never a fork.
The counter is the load-bearing part. An id is never handed to a second marker, so a Version taken today still names the same thing next month. If those ids were array indices, or derived from a position, the correct declaration would be weak and this adapter would have no business taking part in a restore.

2. Registration

example.markers
vendor.domain. Two authors both shipping markers is a collision; example.markers and someoneelse.markers is not.
display only
Renaming it does not change the adapter’s identity. Ids live inside every Version ever taken; display names do not.
implementation
Bump for any change.
record shape
A different number entirely, and it moves only when an older record can no longer be read correctly.
persistent
Honest here, because the counter above is persisted and never reused.
The registration happens from the hook, in lua/autorun/server/. This addon may load before or after Blueprints, and adding a handler works in both directions.

3. The dependency

Optional, and cheap: it runs before every capture. Note what it actually tests. Not “does the global exist” but “is the thing I am about to read ready to be read”. While it returns false, the domain is left out of Versions rather than recorded as empty.

4. Capture

Every marker on this map, or nothing. ForMap sorts by id, so two consecutive captures name the same objects in the same order. source = "adapter" because the id came from this addon’s own durable store; persistent because that store outlives a restart. The coordinates are plain numbers. Never a Vector or an Angle: a Version is written to disk as JSON, and an engine handle in it makes the file unwritable and the record a lie.

5. Transform

The unknown-id branch is not defensive padding. Blueprints asks about ids that cannot exist, and false plus a non-empty reason is the contract.

6. Materialize

Two things worth stopping on. orientation = "yaw". A marker is a point with a facing, and pitch or roll would be a promise this adapter cannot keep. Declaring it here is what stops a post-restore check reporting a perfectly placed marker as mis-rotated on two axes nothing ever wrote. preview = { kind = "point" }. No model, and that is a first-class answer rather than an omission. An adapter with geometry would return { kind = "bounds", mins = ..., maxs = ... } instead.
The rebuilt marker is a different object from the one the Version recorded. A migration that reused the id could not tell them apart.
The third function, and it is not optional. An adapter that can create but cannot undo makes an all-or-nothing group migration a lie the moment a later member fails.

7. Properties

Only what Collect captures, and nothing else. Note that orientation = "yaw" appears again, in PrepareProperties. That is not a copy and paste mistake: because this adapter declares properties, post-restore verification asks this function, and an adapter that declared yaw in one and nothing in the other would be verified as "full". The read-back is the capability contract made real rather than promised.

8. Remove

9. Trying it

The addon ships a console command so the example can actually be exercised. As an admin, in the server console or in game:
Add a few, then run the contract against it:
The example declares all five capabilities, so the full run exercises every check the runner has, with nothing reported as not run, and leaves the same markers behind. Then follow the testing guide for what no runner can do for you.

What to copy from it

  • Registration from the hook, in lua/autorun/server/.
  • A persisted counter for identity, and the honesty to declare persistent only because of it.
  • Refusing unknown ids with false plus a reason.
  • Declaring orientation from both prepare functions.
  • Reading values back after writing them.
  • A dependency check that tests readiness, not existence.