Reference6 min read5 sections

Private player accounts

docs/PLAYER_ACCOUNTS.mdSource updated 2026-10-27
Markdown
On this page 5 sections

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:

Terminal
DW_PLAYER_ACCOUNTS=1 npm run dev:persistent -- --rebuild

The room must exist before granting membership. In another terminal using the same database connection, create an account and grant its printed ID access:

Terminal
npm run player:account -- create Rowan /tmp/rowan-account.json
npm run player:account -- grant ACCOUNT_ID lantern-house

Open 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:

Terminal
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.json

Run 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:

Terminal
DW_PLAYER_ACCOUNTS=1 npm run dev:rooms -- --rebuild
npm run player:account -- grant ACCOUNT_ID route:local:lantern-house:1

Grant 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:

Terminal
node tools/cli/test-control.mjs --test-name-pattern='account doorway|browser doorway' tests/room-routing.test.ts

Still 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.

End of Player accounts