The runtime distinguishes stable entity IDs from process-local index/generation handles. Authored objects keep their persisted source IDs; changing a display name, array order or release label does not mint a new ID. Legacy world format 1 scopes authored object IDs to a place. The primitive compiler now emits an explicit world-global mapping as authored: plus SHA-256 of [world.id, place.id, object.id]. Release labels, display names, row order, transforms and materials do not change that identity. Renaming a legacy source ID or moving it to a different source place changes that default identity. World format 2 stores an explicit persistent entityId; authoring import and transactions retain it through rename/move operations. New authoring commands prepare UUIDv4 identities once at ingress. See the compiler contract.
Spawned persistent entities use spawned:<lowercase UUIDv4>. The namespace is reserved: authored source objects cannot claim it. Production allocation uses the platform's cryptographic crypto.randomUUID(); missing entropy fails rather than falling back to clocks, Math.random, simulation seeds or process counters. UUID version/variant, casing and exact length are checked. UUIDv4 provides probabilistic global uniqueness, not a mathematical collision guarantee; RFC 9562 §5.4 specifies its layout. Persistent IDs are public labels, never permissions or secrets.
#Command preparation and replay
preparePersistentSpawn(components) returns an ordinary bounded ECS create command containing the generated ID and PersistentIdentity: {id}. Preparation copies component data and rejects an explicit identity override. It does not mutate an EntityStore or validate game-specific component contracts. The accepting store still validates the complete transaction. Register PersistentIdentity and install the identity invariant before consuming these commands.
Prepare the command once at trusted ingress, retain it with the accepted command record, and submit it through transact or deferTransaction. Retries and replay consume the recorded command, including its ID. Do not call the allocator again for a retry or from a deterministic system tick. An optional injected UUID source exists for trusted test fixtures; it is not a client-selected source in the multiplayer protocol.
A failed transaction leaves the prepared ID available for a retry and changes no live state. A live-ID collision rejects the whole transaction without silently allocating a replacement. A repeated accepted create also rejects as a duplicate. Transaction labels remain labels: these APIs do not add durable request deduplication or automatic idempotent success responses.
The allocator holds no counter or seed that could rewind during checkpoint restoration or fork. Every new ingress command uses a new platform UUID. Allocation does not depend on ECS row order, runtime slots, connection identity, authority epoch, current place, engine version or world release. Travel and ownership transfer preserve the recorded ID.
#Runtime enforcement
installIdentityInvariant(store) checks every accepted final state, including direct edits and restored snapshots. The real Simulation installs it at startup and on independent checkpoint restoration.
- Every entity in the reserved spawned namespace must have a canonical ID and a matching
PersistentIdentitycomponent. - Any entity carrying
PersistentIdentity, including an authored entity, must have a component ID equal to its ECS entity ID. - Removing the identity from a surviving spawned entity or changing it to another ID rejects atomically. Deleting the entire entity is valid.
- Runtime slot reuse still advances generations; an old handle cannot mutate a replacement. Baseline restoration rebinds by stable ID and invalidates old handles.
Legacy authored content remains readable; world format 2 adds persisted authoring IDs; the time contract upgrades local saves to format 2. Session entities such as player.local need no durable identity. Component schema bytes are unchanged. Simulation checkpoint profile 4 binds compiler 2; explicit profile-1/profile-2/profile-3 upgrades preserve existing identities and native state. Existing stored data that violates the new identity invariant is rejected rather than silently rewritten.
This in-memory invariant is not a durable tombstone ledger. Explicitly creating an old deleted ID is not detected after its history has been discarded. Callers must never intentionally reissue a recorded identity; ordered durable request deduplication, deletion retention across reconnects and cross-host ownership are later networking/durability work. No generic ECS API is an authorization boundary.
#Evidence
tests/identity.test.ts covers canonical UUID rejection, entropy failure, a 4,096-allocation sample, command-copy isolation, validation retry without reallocation, collision rejection, forged/removed identity rejection, malformed baseline rejection, stale handles, namespace separation and full Simulation checkpoint/deletion behavior. Sampling detects implementation failures; it does not prove universal collision freedom.
The shared persistent-identity.1 fixture prepares two fixed commands, transports them through JSON, replays spawn/update/delete at ticks above 2^53, and compares complete ECS checkpoint states. A restored snapshot retains the survivor and excludes the deleted entity. The golden fixture has digest 67fdfa979e3fb60d490e38e3f29466cad842eff77a4b482d3add0f265c67908b.
Engine Observatory includes this fixture in Runtime data contracts. The worker also exercises real platform entropy independently of the fixed command log; Playwright compares the entire returned evidence with a fresh Node run. Network entity integration, durable deduplication, live content/state activation and the full milestone 1 acceptance remain open.
Clean source 3e4607cfbb0a0824a274f83f8a6ece27ce261a57 passed all 18 browser scenarios, including the complete Node/browser identity comparison in the capability report. All 78 headless tests passed during development. An initial pickup-return visibility failure did not recur in an isolated run, three repeated handoffs or final full acceptance; its cause remains unproven, with diagnostic capture retained. The post-browser single-player benchmark measured 0.1257 ms p95 over 1,800 ticks; this does not establish larger-world or multiplayer capacity.