An invited player can sign in on another browser and return to their existing room traveler, inventory and progress. The account screen also offers an explicit Link this browser’s traveler action for a current guest. Linking requires a valid guest credential and retires that guest credential after successful account admission. A traveler can belong to only one account, including after revocation. Player account in the top bar reopens the chooser. Guests remain supported.
#Run it
Use PostgreSQL and an account-capable persistent engine from this branch. Set
DW_PLAYER_ACCOUNTS=1 before starting the room or regional coordinator. For a
fresh local room, with the usual PostgreSQL connection variables already set:
DW_PLAYER_ACCOUNTS=1 npm run dev:persistent -- --rebuildThe room must exist before granting membership. In another terminal using the same database connection, create an account and grant its printed ID access:
npm run player:account -- create Rowan /tmp/rowan-account.json
npm run player:account -- grant ACCOUNT_ID lantern-houseOpen the room’s printed /?persistent=lantern-house URL. Enter the key from the
private file, then choose your account traveler or link the existing guest.
Repeat in a separate browser profile to return to the same traveler. The room
passphrase still applies. Do not put the account key in a URL, source file or
screenshot. The application never sends invitations automatically.
Existing rooms keep their retained engine; rebuilding alone does not upgrade them. Follow room upgrades before enabling this feature on an existing release. The regional doorway now offers account sign-in and an invited-room selector; exact persistent-room URLs remain supported.
#Hosting and account recovery
compose.persistent.yaml and compose.rooms.yaml expose the opt-in environment
variable. Account tables live in the existing PostgreSQL volume, under
dw_identity; engine bundles remain in /data. The public control migration
history is unchanged. Keep the exact public HTTPS origin in DW_ALLOWED_ORIGINS.
The cookie is HttpOnly, SameSite=Strict, and Secure for HTTPS deployments.
Both persistent and regional Docker images include the operator command:
node accounts-dist/player-account.js create Rowan /data/rowan-account.json
node accounts-dist/player-account.js grant ACCOUNT_ID lantern-house
node accounts-dist/player-account.js rotate ACCOUNT_ID /data/rowan-new-key.jsonRun these in the application container’s terminal using its database connection.
The command writes the credential file with mode 0600 and syncs it before
changing the account. It prints the ID and file location, never the key. Give
only the key to the verified owner; remove transferred private files when no
longer needed. If acknowledgement fails, retry apply PRIVATE_FILE against the
same file. A superseded replacement cannot restore an old key.
Other commands: list, rooms ACCOUNT_ID, revoke ACCOUNT_ID ROOM, and
disable ACCOUNT_ID. Rotation revokes every remembered device and issued room
credential, retaining the traveler binding. Logout revokes this browser’s account
session and its room credentials. Revocation takes effect on subsequent
admission/reconnection; already connected sockets are not immediately evicted.
Stop/restart affected rooms when immediate removal is required.
Remembered login sessions last 14 days, with at most eight sessions per account.
Room credentials issued from them cannot extend that lifetime. Account keys,
login tokens and room tokens are hashed in PostgreSQL. Browser storage holds the
ordinary room credential, not the account key. player:recover refuses linked
travelers; recover their account with player:account rotate instead.
The backup command now includes the account schema, memberships, sessions and credential associations, validates their digests, and restores them alongside room state. Keep backups private: restoring a snapshot also restores credentials that were valid at that snapshot. Older backup binaries do not include accounts; use this branch’s rebuilt backup image.
#Focused acceptance and follow-up
Cooperative and trusted service checks pass for cross-device identity, explicit link proof, exclusive ownership, guest-key retirement, repeatable binding, logout, revocation, replacement keys and guest admission. A real software-WebGPU browser journey returns to the same inventory on two isolated browser profiles, reloads without creating a duplicate traveler, and verifies that the account key is absent from local storage. The account screen was visually reviewed. The archive restore test preserves account membership and binding with the exact database snapshot. The operator CLI check exercises private file creation, apply retries and superseded-key refusal.
Run npm run build:accounts, then
node tools/cli/test-control.mjs tests/player-accounts.test.ts. Set
DW_ACCOUNT_CLIENT_DIR to a freshly built client and configure Playwright’s
browser executable to include the real browser journey. Type/import checks,
client build and the bundled account command are checked.
Return to these without treating them as passed:
- Retained engine rollout across production releases. The focused gateway tests exercise an account-capable packaged runtime and model an older health response; this does not qualify every historical engine or an in-place production upgrade.
- Immediate live-socket revocation, multi-tab/device handoff, expiry and pressure storms, process interruption and production backup/restore drills.
- Account-backed workspace roles are now available through explicit invitations. Public signup, password reset, email delivery and federated identity remain outside this private account flow.
- Hosted Coolify/container exercise, narrow-screen accessibility and the full supported-device matrix. The final character/environment/arrival art review remains required; current character art has not been approved.
#Regional doorway
Start the regional service with DW_PLAYER_ACCOUNTS=1, using the normal database,
origin and room-passphrase configuration from regional setup:
DW_PLAYER_ACCOUNTS=1 npm run dev:rooms -- --rebuild
npm run player:account -- grant ACCOUNT_ID route:local:lantern-house:1Grant an existing room. To provision an empty slot without a guest, use the
existing operator control service's /v1/rooms operation with that slot ID and
its configured immutable release. This pins the slot to that release; it does
not start a player or change an existing room's engine. Existing invitations
continue to target their retained room. New room creation and publication remain
operator actions, not privileges granted by player sign-in.
Open the regional root URL (normally http://localhost:8794/), choose Player
account, sign in, then choose an invited room and enter its passphrase. Return
to my traveler resumes the linked traveler; Begin my account journey creates
one stable account traveler for an unused invitation. Neither path allocates a
guest first. The chooser lists only enabled invitations in this region. An empty
list explains that an invitation is needed; it does not silently make a new room.
Guest journey retains the existing matchmaking flow. Account credentials are not reused as guest credentials. Signing out clears this browser's saved account room tokens and restores a previous guest credential when one was retained. Browser account keys are cleared from the form after submission and never stored in local storage. The gateway rechecks membership at admission; no client-supplied traveler ID can select another account's inventory.
Focused acceptance: trusted and cooperative gateway tests return one traveler across two login sessions, open a ticketed shared connection, reject other room invitations, reject revoked credentials, and refuse hosts without account support. The existing guest capacity check passes. The real software-WebGPU journey returns to the same lantern inventory from two isolated browser profiles, enters the live world, returns to the doorway, signs out and selects guest mode. It passes in 17.1 seconds. Its first run exposed a test fixture origin configured after server creation; setting the origin before construction fixes that fixture. The doorway screen was visually reviewed. Type/import and regional-service build checks pass.
Test source: tests/room-routing.test.ts. Set DW_ROUTING_CLIENT_DIR to a freshly
built client and configure Playwright, then run:
node tools/cli/test-control.mjs --test-name-pattern='account doorway|browser doorway' tests/room-routing.test.tsStill review narrow screens and assistive technology, simultaneous account tabs, stale invitation lists, saturated pools, long sessions and deployed rollout. The existing immediate-revocation and final art review notes above remain open.
#Carrying an account traveler between rooms
Invited accounts can now use the same menu and authored room doorways as guest travelers. The destination must invite the account and have no different traveler linked to it. The transfer preserves one actor and its inventory, binds the new room credential to the same account session, and rechecks permissions before commit. An old room link follows that traveler to its current room when signing in on another browser. See demo, permission behavior and remaining review.