Skip to main content
Blueprints copies every value you return into a freshly allocated table, so a Version never holds a reference to anything of yours.
Blueprints assigns the namespaced id (acme.turrets:turret_7) and owns metadata. Do not set either.

localId, required

Unique within your adapter, within one capture. Must match [A-Za-z0-9_-]+. This is the identity. Read Identity before choosing how you generate it: it is the field that decides whether a restore matches the right object or destroys the wrong one.

type, required

A non-empty string in your own vocabulary: acme_turret, darkrp_job_spawn, example_marker. Blueprints never interprets it. It is shown to humans and compared between captures, so a type that changes for the same object reads as a replacement.

identity, required

scope may be stronger than your descriptor’s declared scope, never weaker. See Identity.

transform, optional

Six finite numbers. Plain arrays, never Vector or Angle. Omit it entirely for configuration that has no position. If you supply it, both pos and ang are required: an object with a position but no meaningful facing supplies ang = { 0, 0, 0 } and declares orientation = "none" from PrepareMaterialize.

bounds, optional

Model-space and unrotated. Blueprints rotates all eight corners itself, so it stays correct for a pitched or rolled object. This is spatial metadata, not a property: it is what lets a group be set down on a surface using the numbers your server recorded, rather than whatever copy of a model the admin’s client happens to have installed. Supply it if your objects have geometry; omit it if they do not.

properties, optional

Flat scalars only: string, number (finite), boolean. No tables, no nested structures, no nil. Keys are strings and are yours to choose. Blueprints compares them between captures to decide what changed, and (if you declare the properties capability) writes them back during a restore.
The set you capture is the set you can restore. If you capture ammo you must be able to write ammo back and read the same value. If you cannot, leave it out.
Numbers compare with a small epsilon, so a float that loses precision through JSON is not reported as a change forever after.

label, optional

A human-readable name, for reports and the interface. Never used to match, never diffed for classification. Rename freely.

What is prohibited, always

A Version is written to disk as JSON. An engine handle in a record makes the file unwritable and makes the record a lie the moment the entity is removed. Convert to plain numbers at the boundary:
Blueprints refuses a record containing any of these, names your adapter and the field, and fails the capture.

What you get BACK is a different shape

This is the single easiest thing to get wrong, so it is stated plainly. What you return from Collect is the table above. What Blueprints hands back to you (the source argument of PrepareMaterialize, Materialize, PrepareProperties and ApplyProperties) is the normalized record, and two of your fields have moved:
Reaching for source.localId or source.label gets you nil, silently, on a path that only runs during a real migration or restore.

Immutability

Those records are defensive copies, and you must treat them as read-only. Writing to one is a contract violation.
The copy contains the damage, so nothing of Blueprints’ is corrupted, but the adapter is at fault.

Determinism

Two consecutive Collect calls on an unchanged world must name the same objects and describe them identically. That is stronger than it sounds. It rules out:
  • iteration order that depends on Lua’s hash order, where it decides ids;
  • timestamps, tick counts, uptimes or random values in properties;
  • a value read from a source that is still initialising.
Unstable fields do not merely look untidy: they make every diff report every object as changed, for ever, which makes the diff useless and a restore enormous. See Diff semantics.