Skip to main content

Before you ship

1

Use stable identities

From something the host system durably stores. Never from a position, a model, a label, an array index, or an entity index. Identity
2

Declare capabilities truthfully

A capability is a promise about every record you emit. Under-declaring is free. Capabilities
3

Declare the weakest identity scope you emit

session is honest. persistent you cannot back is a silent wrong match.
4

Return deterministic records

Two consecutive captures of an unchanged world must be identical. Diff
5

Return everything, or error()

Never a filtered subset. Error handling
6

Refuse in phase 1, with a code and a reason

Phase 1 never writes and never throws.
7

Read values back before reporting success

A value that did not stick must be reported by you, not discovered by a verification.
8

Return a NEW identity from Materialize

Never the source’s. Migration
9

Validate the host system properly

Prove the functions and the schema, not the existence of a global. Persistence
10

Run the contract

vetra_blueprints_adapter test <your id> from any console, then vetra_blueprints_adapter test <your id> full from the server console of a development server. Testing
11

Restart the server and check your ids

The one test nothing else can do for you. Testing

The eight that do real damage

1. Using an entity index as a persistent identity

Entity indices are reused within seconds of an entity being removed. After a restart they name completely different things, so a restore matching on a recycled index applies one object’s recorded state to another. Do: use your addon’s own durable key, and declare session unless you genuinely have one. If your domain is entity-backed, Vetra.Blueprints.Entities.Identity(ent) handles both cases for you.

2. Deriving identity from position, model, or any property

The first time an admin moves that turret, its id changes, and a changed id is a delete plus an add. The restore acts on the delete. Do: read Identity.

3. Storing a Vector, Angle or Entity in a record

Versions are JSON on disk. A Vector makes the file unwritable; an Entity makes the record a lie the moment it is removed. Do: plain arrays of finite numbers.

4. Claiming a capability you cannot honour for every object

properties = true when most records work and some do not is not a partial capability, it is a false one. Blueprints builds all-or-nothing group materialization and rollback-on-failure on the promise, not on the average. Do: declare less, and refuse the impossible cases in phase 1 with both a code and a non-empty reason.

5. Silently omitting objects from Collect

An object left out of a capture reads, in the next diff, as an object somebody deleted. Do: return everything, or error(). A capture that failed is recoverable; a capture that lied is not.

6. Reaching for a field that is not on the record you were given

What Collect returns and what Blueprints hands back are different shapes. This only bites on a path that runs during a real migration or restore, which is why it survives casual testing. Records The same class of mistake: indexing transform.pos in an adapter whose records have no transform. The argument is nil there too.

7. Mutating a record Blueprints handed you

The source argument is a defensive copy of a Version’s record, and it is read-only. The same goes for your own descriptor: it is copied at registration, so self inside a capability function is Blueprints’ copy. Keep mutable state in your own upvalues.

8. Assuming load order, or registering outside the hook

Do: register from Vetra.Blueprints.RegisterAdapters, as Registry shows. Registration outside that window is refused, and the refusal tells you this.

Smaller ones, worth a look

It is in every Version ever taken. Rename the name field instead; nothing matches on it.
It runs before every capture. A table lookup or one indexed query, not a world sweep.
The rebuilt object is a new object. Return a new localId.
Verification will report every object as mis-rotated on two axes. Declare "yaw", or "none" if there is no facing at all.And if you declare properties, return it from PrepareProperties too, because that is the one verification actually asks.
It is not copied, so self.MyHelper is nil. Registration warns about it; use upvalues.
acme-games.turrets is refused. The pattern is [a-z0-9_] on each side of one dot.
It may not. The preview degrades to an outline, which is correct. Do not work around it.
There is no client half. Adapters are server-only, and previews cross the wire as declarative data rather than as code.
Your adapter writes your domain. If another persistence system has claimed an object, it is not yours to capture and not yours to destroy, even if it looks like one of yours.