A room owner can replace a lost browser credential without creating a new traveler or moving inventory between identities. Recovery keeps the existing player ID, pose, inventory and private rewards. It changes only access, the room's fencing epoch and the durable operation sequence/receipt.
This is an operator maintenance workflow, not public account signup. Verify which person owns the saved traveler before issuing access. Names are not unique: select the exact traveler UUID from the operator listing.
#Operator workflow
Use the PostgreSQL connection variables for the affected deployment and set
DW_PERSISTENT_ROOM to its exact room ID. The CLI requires server/database access;
never put those credentials in a player browser.
export DW_PERSISTENT_ROOM=lantern-house
npm run player:recover -- list
npm run player:recover -- prepare ACTOR_UUID 'https://your-host/?persistent=lantern-house' /private/rowan-recovery.jsonReplace ACTOR_UUID, the player URL and the private output path. The URL must
already target the correct persistent room, including its retained engine path
or physics query if required by the deployment. Preparation creates a new file
with mode 0600 and refuses to overwrite one. It does not change access. The
file retains the operation ID, credential precondition and replacement key so
an interrupted application can be retried exactly. CLI stdout contains metadata
and the file path, never the replacement key or private link.
Stop the affected room and its supervisor before applying. For a routed deployment, stop the coordinator that could immediately restart it as well. A live room lease rejects recovery; do not bypass this check.
npm run player:recover -- apply /private/rowan-recovery.jsonAfter success, restart the room. Read the url field from the private file and
give it to the verified traveler. Keep the file private; its link grants access
to that traveler. The browser shows Your journey is still here and asks the
recipient to choose Restore my traveler. It removes the secret fragment from
the address immediately and keeps the replacement only in memory until normal
room admission accepts it. Choosing Keep my current journey leaves the
browser's existing key unchanged. Normal room-passphrase rules still apply.
Once admitted, ordinary browser storage retains the new return credential. Reloading uses the same saved traveler. An invalid replacement cannot overwrite a previously stored credential. A new blank browser does not silently select an identity just because someone sent it a recovery link.
#Durability and revocation
Recovery locks the room row and checks the exact saved credential set. It removes
all old credentials for that traveler, installs the new hash with the ordinary
30-day admission lifetime, fences the old room lease, and writes a durable
credential-recovery receipt/outbox event in one PostgreSQL transaction. It does
not change the world release, player revision, pose, item ownership or awards.
No new database migration is needed; existing backup table coverage includes the
changed rows. Full backup/restore qualification of this new event remains open.
Retry apply with the same private file after an uncertain response. The same
operation returns its receipt without replacing credentials again. Reusing the
operation for different inputs fails. If another recovery replaced the key or
it expired, an old operation cannot revive it: prepare a new file. Credential
preconditions reject competing/stale recovery plans. Do not prepare separate
replacement files merely because a response was lost.
The room must be stopped because live browser and gateway connections retain admitted identities. Recovery does not attempt unsafe mid-session revocation. The changed room epoch fences previous owners; restarted admission rejects the old key. Room-ticket checks still require current room generation and ordinary session validation. Crash/partition and routed-ticket fault combinations remain in final qualification.
#Internal API
The existing private control service also exposes these operator-only POSTs:
/v1/players/listwith{room}returns bounded traveler identity metadata and a credential-set fingerprint, not keys or credential hashes./v1/players/recoverwith{room, actor, operation, credentials, credentialHash}executes the replacement.credentialsis the exact fingerprint from the listing;credentialHashis SHA-256 of a fresh random 256-bit key generated and securely retained by the operator before the call.
The control bearer credential is mandatory and browser Origin requests are
rejected. These endpoints are not forwarded by the public room or doorway.
#Scope and remaining review
The browser confirmation requires the updated player client. Recovery does not implicitly upgrade a retained engine or migrate content; an older pinned client must receive a compatible, explicitly managed update before it understands the link. Use the ordinary retained-engine deployment procedure.
Global accounts, automatic identity verification, email/social login, world membership tied to a global player account, self-service recovery, live-session revocation and cross-region account failover remain separate unfinished work. Review real operator deployment/stop procedures, older engine compatibility, process-kill/fsync faults, routed tickets, lost-response retries over real networks, backup restore, mobile layouts and screen readers during qualification.
#Focused acceptance
Four native/service/CLI checks pass against fresh PostgreSQL: cooperative and trusted inventory-preserving recovery, active-lease refusal, transaction rollback, old-key rejection after host restart, retry receipts and superseded-key refusal; operator authentication/origin rejection and the real recovery endpoint; private CLI preparation followed by stopped-room apply and exact retry without printing the secret. The real software-WebGPU browser journey passes (14.0 seconds): confirmation before credential storage, restored identity/inventory, ordinary reload, cancellation, invalid-key rejection and preservation of an existing valid key. Its first extended run exposed same-document hash navigation skipping the confirmation; the player now reloads that navigation through the ordinary confirmation path, with listener cleanup on disposal.
Type/import checks, the client build and the control-service bundle pass.
Confirmation screen and the restored in-world
view were visually reviewed. Test source: tests/player-recovery.test.ts.
Run native checks through node tools/cli/test-control.mjs tests/player-recovery.test.ts; set DW_RECOVERY_CLIENT_DIR to a freshly built
client and configure the Playwright browser to include the browser journey.
These focused checks do not close the broader qualification items above.
Account-linked travelers now use account key replacement. The room-key recovery command refuses these identities to preserve account ownership.