Reference43 min read26 sections

Regional persistent rooms

docs/ROOM_ROUTING.mdSource updated 2026-10-27
Markdown
On this page 26 sections

#Vehicle and passenger room transfers (F66)

A driver can now use an authored visible room doorway while seated. The cart, connected body island, seated/standing riders, their inventories and arrival poses transfer in one fenced PostgreSQL transaction. Passengers follow the coordinator's committed room change through the player handoff flow. A retry returns the committed result. Every account rider must have destination access.

Try a fresh private two-host fleet world:

Terminal
DW_CROSSING_VEHICLES=1 DW_CROSSINGS_DATA_DIR=.data/fleet-crossings.1 npm run dev:remote-crossings

Open the printed lobby in two browser profiles, join the same first room, and board the cart together. The first seat drives. Approach the brass arch and press Interact when Travel with the cart and passengers appears. The passenger follows automatically and retains their seat. Drive back toward the destination's arch to return. Use fresh storage for migration 15; retained older engines keep their original database contract. This does not deploy a production application.

Routed trusted worlds with seated vehicles use a release-wide fleet. The first room to claim an authored vehicle owns that island; another compatible room starts with its dormant catalog row. dw_vehicle_owners records the sole room owner. An exported row uses engine:away, retains its motion for compatibility and has no rendered body or local collision. Returning imports the same identity. Other authored props remain ordinary room-local objects.

The source freezes all riders; the destination prepares collision and holds its active travelers briefly. The SQL commit fences both leases and physics revisions, then changes fleet ownership, native checkpoints, rider locations, credentials, keepsakes and inventory together. Both hosts reload the committed native state before releasing their holds. A refused fit/arrival preserves the source. Crossings need the same release and physics backend and use each room's in-process native owner; separate per-place physics processes remain unsupported for this room-host vehicle path.

Focused delivery smoke uses a temporary PostgreSQL database: a driver and passenger transfer with one fleet cart, duplicate delivery returns the receipt, the source replica removes collision and the destination restores both seats. Type/boundary/wire checks pass. Full browser/network fault, account-revocation, capacity and deployment qualification follow feature completion. No new rendered screenshot was captured for this transfer increment.

The regional doorway places travelers into a compatible persistent room and starts another instance when its seats are occupied. One public HTTP/WebSocket service serves each room's exact retained client and routes to private, supervised room processes. The first deployment topology is one coordinator per region on a Linux host or Linux container.

The new doorway uses Darkwind's green, amber and serif styling. Enter the world finds a room; Continue your journey returns this browser to its saved room, including when that room uses an older release. Inventory and world state belong to their original room. A new instance starts an independent journey. Opt-in guest crossings now move an existing traveler and inventory between compatible room host processes; see the crossing section below.

#Run locally

Use Node 22.23.4 and a dedicated PostgreSQL database. Set its connection variables, or use DW_DATABASE_URL instead:

bash
npm ci
export PGHOST=127.0.0.1
export PGUSER=darkwind
export PGDATABASE=darkwind_rooms
export PGPASSWORD='your-database-password'
export DW_DATA_DIR=.data/rooms
export DW_ROOM_PASSWORD='your-private-playtest-passphrase'
export DW_ALLOWED_ORIGINS=http://localhost:8794
npm run dev:rooms -- --rebuild

Open http://localhost:8794/. Use separate browser profiles for different travelers. The default doorway offers the trusted Lantern House, with up to four live instances and eight travelers per instance. Those are admission limits, not a qualified 32-player performance claim. The browser acceptance fixture uses three travelers across two rooms with a two-player cap.

To invite someone into your specific room, share the address shown after entry. It contains the room and engine identity, not your private return credential. A new traveler enters the passphrase; a full room refuses admission rather than moving them into another instance without their choice.

The editor remains a separate command and storage profile. After building once, npm run dev:rooms reuses engine-dist; use --rebuild when deliberately packaging a new candidate. A rebuilt source engine can create a new compatible-release group; it never replaces an existing room's engine or moves its travelers automatically. Restarting without rebuilding preserves that source candidate.

The browser keeps a private return credential for each room and a last-used room pointer for each regional world. Clearing site storage loses that browser's access; the database journey remains. Begin in another room updates the last-used pointer after successful admission. Earlier credentials and room invitations remain valid. Opt-in player accounts now provide cross-browser return and an invited-room list. Guest journeys remain browser-scoped.

#Account invitations

Set DW_PLAYER_ACCOUNTS=1 on the regional coordinator to show Player account and Guest journey choices. An account sees its enabled room invitations in this region and can return to the same traveler from a second browser. Room passphrases still apply. Invitations target explicit existing room IDs; player sign-in cannot provision or publish rooms. See account setup and review notes.

#Region and world configuration

DW_REGION defaults to local. A caller must request this exact region; the gateway refuses another region instead of silently changing geography. Deploy a separate coordinator/address for another region. Automatic geographic discovery and cross-region failover are not implemented. Guest crossings currently stay within this coordinator and its local room host processes.

For explicit policy, point DW_ROOM_ROUTES at a trusted JSON file:

json
{
  "format": 1,
  "region": "west",
  "maxActiveRooms": 2,
  "worlds": [
    {
      "id": "lantern-house",
      "name": "The Lantern House",
      "profile": "trusted",
      "instances": 4,
      "playerCap": 4
    }
  ]
}

Each world selects exactly one of:

  • profile: trusted or cooperative, publishing the corresponding built-in Darkwind template with the current source engine.
  • release: an existing immutable PostgreSQL release hash, for a custom world published through the trusted control service. Its retained engine must implement routing ABI 1 and the current database schema.

Names are display labels; changing a name does not change the authored world. Region and world IDs use lowercase letters, digits and hyphens, beginning with a letter. They are at most 32 characters. Limits are eight configured worlds, sixteen instance slots overall, eight live room processes, and eight travelers per room. The configuration file is at most 64 KiB. The production defaults are smaller than the maximum configurable pool.

Room IDs are stable: route:<region>:<world>:<instance>. Do not change region/world IDs or remove slots containing journeys you still intend to serve. New arrivals reuse a live compatible instance, then a stopped compatible instance, then an unused slot. Compatibility includes the immutable release and retained engine. Full rooms include both connected travelers and outstanding arrival reservations. Older incompatible instances remain accessible to their returning travelers but are excluded from new matchmaking.

After all configured slots have retained releases, publishing another candidate does not reclaim them. Choose an explicit retained release, increase the bounded slot count, or deliberately upgrade selected rooms with the stopped-room upgrade workflow. Stop the regional coordinator before applying such an upgrade so it cannot start the room while maintenance is underway. A renamed world ID creates a different group of journeys.

One PostgreSQL advisory ownership connection prevents two coordinators from managing the same region in the same database. Each child also holds its normal fenced room lease and epoch. A coordinator that loses its ownership connection stops admitting and shuts down. This does not fence two independently restored database copies; stop the original deployment before promoting a restore.

#Admission and routing boundaries

Origin checks, a private passphrase and request limits protect new admission. A valid room return credential can resume that room without prompting for its passphrase again. The gateway validates credentials against their room-scoped hashed database records. Credentials for another room cannot be used to resume or request a ticket here.

Every routed WebSocket requires a thirty-second, single-use signed ticket. Its audience includes region, room, traveler, engine, release and lease epoch. The gateway keeps a bounded arrival reservation, consumes the ticket before forwarding, and tells the private runtime the authenticated actor. The runtime requires the hello credential to identify that actor. A ticket for another room, another actor's hello, a replay, an old generation or an expired ticket fails. Each reconnect obtains a fresh ticket; long-lived return credentials stay in request bodies and WebSocket hello messages, not URLs.

Children listen on Unix sockets under a private temporary directory, not public TCP ports. Do not expose those sockets or the internal control service. Only the public doorway receives a Coolify domain. The gateway forwards no browser-supplied actor identity header to a child.

The passphrase protects participation. Client assets and world definitions are still available through public URLs. Private player fields continue to use recipient-filtered replication. This release does not provide private asset distribution, multi-tenant creator isolation, account roles or arbitrary untrusted script hosting.

The gateway bounds pending requests, configured instances and process count. Admission is serialized so concurrent arrivals cannot overbook reservations. It caches at most four immutable world documents and constructs each through its retained compiler. A quick private health probe requires live lease evidence, the expected room/release/engine and the configured player cap; a listening socket alone is insufficient.

Measured runtime pressure now governs new admission; saved returns retain access to their original room. See host health and operator inspection for metrics, soft budgets, retained-engine compatibility and the separate operator token.

#Recovery and idle rooms

Each child uses the external host watchdog. A failed child is removed from the live directory and its tunnels close. A returning client's ticket request starts that room again with its pinned engine; another room continues independently. A temporarily unresponsive instance is skipped for new arrivals when a healthy compatible instance has space.

If the process pool is full, a room may be parked only when it has no active travelers, unexpired arrival reservations or pending durable work, and has been idle for at least sixty seconds. Parking drains and retires the process; it does not delete its database records or retained engine. Occupied rooms are never evicted to make space. If no process can safely be parked, admission reports that the region is busy.

Stopping the gateway refuses new admission, drains its children and closes remaining tunnels. Child stop deadlines remain eighteen seconds; the gateway has a twenty-five-second process deadline and Compose allows thirty seconds. On restart, the directory reconstructs bindings from PostgreSQL and starts rooms lazily as travelers return. Tickets are intentionally invalid after a gateway restart; return credentials remain valid.

A graceful stop saves final accepted positions. An abrupt kill can rewind movement to the last four-second autosave; acknowledged inventory/rewards remain in the fenced ledger. It does not preserve arbitrary dynamic simulation state. Production RPO/RTO, broad load qualification and full operational telemetry remain open.

#Docker and Coolify

Create a separate Docker Compose resource using compose.rooms.yaml. Set DW_POSTGRES_PASSWORD, DW_ROOM_PASSWORD, and DW_ALLOWED_ORIGINS to your exact public HTTPS origin. Optionally set DW_REGION. Assign the public domain to doorway, port 8794. PostgreSQL has no published host port; child rooms have no public TCP listeners.

Compose creates darkwind-regional-db for PostgreSQL and darkwind-regional-engines mounted at /data for immutable retained engines. Preserve both across redeploys. To use a custom configuration, mount it read-only into the doorway and set DW_ROOM_ROUTES to its container path. Keep the configuration with deployment records; it is needed to restore the same region, world IDs and slot policy.

The existing backup tool captures all rooms and referenced engines in this schema-6 database. Point it at this deployment's database and /data, not at another beta/editor database. Preserve the routing configuration separately. Restoring blocks startup until the archive's verification marker is cleared by the restore tool. The directory and pending tickets are ephemeral; durable rooms, credentials and engines come from the archive.

Guest crossings add control schema 13. Existing databases with retained rooms are not automatically migrated; use the dedicated crossing demo database or an explicitly planned engine/database upgrade. Older standalone engines without routing ABI 1 remain supported by their standalone bootstrap, but the regional gateway refuses them until explicitly upgraded. Do not bypass the retained-engine check or reuse an incompatible schema. Native packaging and Compose validation do not prove that this resource has been executed on a Docker/Coolify host.

#Acceptance

bash
npm run check
npm test
npm run test:control
npm run test:browser:rooms
npm run test:browser:lifecycle:cooperative
npm run test:browser:trusted

The regional browser runner uses isolated PostgreSQL and ports 5175/5176. It builds the production gateway and retained engine, captures the doorway, places two travelers together and a third in another room, checks independent lantern ownership, suspends one child with SIGSTOP, and verifies both affected travelers reconnect while the other room continues. It then restarts the entire gateway and checks same-page identities and the returning-traveler doorway. Transport adds 75 ms each way.

Native tests cover concurrent reservation capacity, failed authentication before room creation, one coordinator per region, ticket audience/replay checks, room-level crash recovery, retained engine selection after redeploy, occupied-room protection, idle parking and degraded-room selection. See the recorded acceptance evidence. Automatic Coolify restart timing, remote CI, multi-region failover and broader capacity/device qualification remain unverified.

#Guest crossings between room hosts

Run npm run dev:crossings on Node 22 and open http://localhost:8796/. The launcher creates a dedicated persistent PostgreSQL database under .data/crossing-rooms.2 and two compatible furnished Lantern House slots. Open two browser profiles, collect the keeper's lantern, then press Esc → Visit another room. Your traveler and satchel move to the selected room; your friend can remain behind or follow. The client loads the destination's retained player page. This is a loading transition, not a seamless portal.

For a configured regional deployment, set playerTransfers: true in the routing JSON (or DW_PLAYER_TRANSFERS=1 with the default configuration). The current furnished reference uses a 1024 MiB RSS admission budget per room; the watchdog now sizes the V8 heap at half that configured budget, preserving native headroom. The former fixed 256 MiB heap could terminate the furnished room during admission. This setting is not an eight-player capacity qualification.

The destination must use the exact same immutable release, engine and physics backend. It must have a free seat and accept admission. The source host freezes its authoritative motion and saves its pose; the journal then prepares against both lease epochs and the saved player revision. A single PostgreSQL commit moves the active player, held items, gameplay resources and keepsakes. Source player records and item records become inactive tombstones. Old source credentials cannot obtain a source ticket or mutate the departed player. Returning to that room reactivates its record without duplicating inventory.

If a destination inventory slot is already occupied, the crossing is rejected; this engine currently supports one instance of each authored collectible per room. Unclaimed destination copies are replaced by the arriving item. Source copies remain hidden while away. Doors and shared story flags stay in their original rooms. Pending ability casts are canceled; resources and completed keepsakes are carried.

Crossing operations have stable IDs. Explicit temporary admission-pressure refusals retry for up to 15 seconds after the first refusal with the same ID; ambiguous network failures retain the request for reload recovery. A lost committed response is recovered by retrying the saved request or reloading; the old room invitation follows the traveler's current room. Failed preparation is aborted and leaves the source owner intact. A frozen source times out after 20 seconds; a replacement host aborts preparations from its prior epoch. A committed crossing is never rolled back because its response was lost. The journal and tombstones are included in the backup table catalog. Existing schema-12 room databases are protected from automatic migration; the new demo uses its own schema-13 database.

Current limits and required follow-up: one regional coordinator and database, same release, no carried vehicle/ragdoll, and no active movement recording during a crossing. Guest and invited account crossings support menu and authored E doorways. Destination preloading, remote-machine routing, cross-release item mapping, vehicle islands, mixed recording/replay, and wider fault/load/device qualification remain open within the full plan. Final art approval remains open.

Focused acceptance: five PostgreSQL journal/ownership checks passed, including occupied inventory, lease replacement, abort, reconnect and round-trip transfer. A live three-traveler test across two independent room processes passed, including observer updates, lost-response retry, old-invitation redirect and rejection of old source tickets. Type, package-boundary and wire-catalog checks passed.

Browser review found a Chromium physical-GPU initialization failure after navigation ("A valid external Instance reference no longer exists"). Destination ownership was already committed. The final physical-GPU browser journey passed in 1.4 minutes: collect the lantern, discard the committed response, reload into the destination with the same traveler/inventory, then have a friend follow and verify exactly one active player/item owner. That successful run did not need the graphics Retry button; this earlier initialization failure remains an open graphics qualification item. The crossing menu and two-traveler destination were visually reviewed. The test permits one explicit Retry for that exact GPU error and reports it when used; this is not proof that the graphics failure is fixed.

Commands:

bash
npm run dev:crossings
node tools/cli/test-control.mjs tests/player-transfer.test.ts
node tools/cli/test-control.mjs --test-name-pattern='live player crossing' tests/room-routing.test.ts
npm run test:browser:crossings

#Authored room doorways

The public SDK's addRoomCrossing(world, placeId, doorway) returns a new format-67 world. The creator sets a static interaction position, reach, label and routing world/instance. instance: 'next' cycles through that routing world's configured instances; an integer selects its one-based slot. The coordinator resolves the route from the pinned world, ignoring a client-provided destination for doorway requests. The source host locks motion and checks the authoritative pose is in that doorway's place and radius before preparing the existing durable transfer. A refusal releases the lock and leaves ownership at the source.

ts
import {addRoomCrossing} from '../../packages/sdk';
const worldWithDoorway = addRoomCrossing(world, world.entry, {
  kind: 'room-crossing', id: 'lantern-road', label: 'Follow the lantern road',
  position: [0, .85, 1], radius: 4.2,
  route: {world: 'hearth', instance: 'next'},
});

npm run dev:crossings now starts the brass Lantern Road reference doorway on localhost:8796, with isolated .data/crossing-rooms.2 storage. Press E at the arch near the starting point. A second browser can follow; E at the destination arch returns to the first room. The previous demo's storage is retained separately. The same transfer journal, inventory rules, retry behavior and guest restrictions apply. Portable game compilation/save/reload includes the doorway. Create it in SDK source or through the editor controls described below.

This is an explicit E interaction and a loading transition. Seamless portal views/preloading, remote-machine routing, destination arrival anchors, sensor/quest gating, moving doorway attachments, wider mixed replay/fault coverage and final art approval remain unfinished. A doorway is not an access-control boundary: guest menu transfers remain available between the same compatible rooms. No new room-access permissions are implied by its label.

Focused checks: tests/room-crossing-sdk.test.ts, the live player crossing case in tests/room-routing.test.ts, and npm run test:browser:room-gateways.

Cold admission now starts its retry window at the first explicit pressure refusal, so the initial room build does not consume that window. The coordinator checks already-running rooms before starting another cold host while an active host is pressured. Doorway routing fetches only the authored interaction from PostgreSQL, without repeatedly loading the world’s model catalog. Initial admission and sustained load remain part of broader capacity qualification.

Acceptance: the SDK portable save/reload check, live three-traveler host check (including an out-of-reach refusal and inventory preservation), and type/boundary/ wire checks passed. The physical-GPU browser journey passed in 1.9 minutes: two travelers used the authored doorway, reunited, and one returned with the same identity. Earlier runs exposed cold-start pressure; the retry budget and cold-host selection fixes above address that observed path, without claiming capacity qualification. The demo arrival was then reframed and reviewed through the actual dev:crossings launcher; see the arrival screenshot.

#Visual room doorway authoring

Run npm run dev:editor, open the private URL printed in the terminal, and choose Room doorways… → Add a doorway. Set the invitation, destination world key, next room or a numbered room, fixed world position and interaction reach. If the draft is not trusted, explicitly enable Use trusted room simulation. Preview shows a gold doorway and reach ring; Save keeps the draft. Reopen the dialog to refine the preview before saving. Saved changes support reload, Undo and Redo, including restoring the original authority and format after the first doorway.

Doorway artwork is separate from its fixed interaction point. Export the saved world to the regional service with crossings enabled and matching configured world/room identifiers and release. The ordinary editor playtest cannot move between room hosts, and the dialog cannot verify deployment configuration. Destination anchors, seamless portal views, remote-machine routing and the final character/environment art pass remain open.

Focused checks: tests/room-crossing-authoring.test.ts covers promotion, exact undo, removal order, route validation and concurrent-edit conflicts; tests/browser/editor-room-crossings.spec.ts exercises preview, save, reload, removal and durable undo/redo. Wider publishing/upgrade and device qualification remain deferred to the integrated release pass.

Acceptance: both focused authoring tests and type/boundary/wire checks passed. The physical-GPU editor journey passed in 4.9 seconds after fixing the new button’s disabled-state wiring. The doorway preview was visually reviewed. Destination room traversal is covered by the prior SDK/runtime increment; this editor check does not qualify a new hosted deployment.

#Invited account crossings

Run npm run dev:account-crossings on Node 22 and open http://localhost:8798/. This uses a separate persistent demo database under .data/account-crossing-rooms.1. Private account files for Rowan and Willow are created with owner-only file permissions under .data/account-crossing-rooms.1/rooms/; the launcher prints their paths. Copy a file's key into Player account sign-in. Both accounts are invited to both rooms. Begin in Room 1 and press E at the Lantern Road arch; a friend can sign in with the other account and follow. An independent browser can sign in with the same account and follow the original traveler from an old room invitation. Do not begin separate travelers in both rooms if you intend to carry one traveler between them.

On regional deployments, enable player accounts and player transfers together. The menu filters destinations to the current account's enabled invitations with an empty or matching traveler slot. Authored doorways enforce the same rules. The matching account session is required; a room credential alone does not permit an account crossing. Destination membership and credential binding commit atomically with player/inventory ownership. Permission is checked again before commit, so a revoked invitation cancels a prepared crossing. An existing different account traveler is preserved and the crossing is refused. Logout, membership revocation and key rotation invalidate the carried account credentials through the existing identity bindings. These account invitations do not disable the separate guest journey option or replace the room passphrase.

Old lobby selections and direct source-room invitations follow the same account traveler to the current room, provided its invitation remains enabled. A new browser receives its own account-bound room credential. Crossings preserve the account choice in browser storage and retain any earlier guest return credential. The account database schema is unchanged; retained room runtimes still need this crossing implementation. Guest-only runtimes are not upgraded by enabling a flag.

Focused checks: the seven cases in tests/player-transfer.test.ts, and account travelers cross in tests/room-routing.test.ts. Broader simultaneous device races, deployment/restore, remote machines, seamless views, full load/device qualification and final character/environment art remain for the closure pass.

Acceptance: seven PostgreSQL transfer cases passed, including account inventory round trips and permission revocation between prepare and commit. The live invited-account host journey passed in 26.1 seconds, including fresh-device return through both lobby and direct old-room URLs. The first host check exposed the obsolete guest-only runtime gate; the next test needed the existing room passphrase added to its direct admission request. Type/boundary/wire checks pass. The dedicated furnished demo then passed a physical-GPU walkthrough: two accounts crossed and reunited, and a fresh browser followed the original traveler from its old source link. The invitation menu was visually reviewed. This does not close broader rollout or art qualification.

#Network room hosts

npm run dev:remote-crossings starts the furnished account-crossing demo at http://localhost:8799/. It runs a coordinator and two separate private host processes (ports 8801/8802), each supervising one retained room runtime. The launcher prints the private demo account-file paths. Sign in as Rowan and Willow in separate browser profiles, begin in Room 1 and follow the Lantern Road arch. This local demo exercises network HTTP/WebSocket routing between processes on one machine; it is not evidence of a two-machine deployment or WAN qualification.

A routing file may define roomHosts with an ID, base URL and tokenEnv (the name of an environment variable containing the private credential). Each world's hosts list assigns one host ID per room instance. Omit it to keep local child hosting. HTTPS verifies the endpoint normally. Plain HTTP is accepted on loopback, or with explicit insecure: true for a private network; the Docker example uses that setting only between internal services. Credentials are kept out of player URLs and diagnostics. Operations diagnostics identify the host assigned to each live room.

The private agent accepts only a matching durable room/release/engine/physics binding, bounded player/room counts and the configured region. It verifies the retained engine bundle before launching it. Authenticated requests carry room session, health and transfer operations; the coordinator validates public admission tickets before forwarding WebSockets and their actor identity. Room hosts keep the existing database leases and watchdogs. Assignments renew every five seconds and expire after 45 seconds without renewal; the agent then stops its owned runtime. A stopped/missing assignment is not silently restarted by the agent. The coordinator can allocate a new assignment on a later admission. Explicit stop waits for the owned process to exit; an unreachable host reports unconfirmed shutdown rather than successful cleanup.

For a Docker/Coolify test, use both Compose files:

Terminal
docker compose -f compose.rooms.yaml -f compose.room-hosts.yaml up --build -d

Set the existing database password, room passphrase and exact public origin, plus distinct DW_HOST_TOKEN_A and DW_HOST_TOKEN_B credentials of at least 32 characters. Assign the public domain only to doorway port 8794. The two workers expose private port 8795 and retain separate named engine volumes; the existing PostgreSQL and coordinator volumes remain persistent. The tracked deploy/room-hosts.json supplies the two internal worker addresses and region local. To use another region, update that file and both workers together.

For separate machines, run the room-host command from the same built image on each host, configure a shared control database and matching public origins, room passphrase, region and account setting, and replace the coordinator's host URLs with the private HTTPS endpoints. Each machine needs the exact retained engine bundle for every assigned room: the current image installs its own bundle, and older bundles must be provisioned from the retained-engine backup. There is no automatic engine upload, worker discovery, cross-region failover or live load redistribution in this increment. Do not route an agent endpoint through the public player service.

Focused verification covers a three-traveler crossing over two authenticated network agents, inventory/identity preservation, old-ticket fencing, retry recovery, invalid credentials/region/release, and expiry of an unrenewed assignment. Actual multi-machine TLS, deployed Docker execution, partitions, coordinator/host restart matrices, rolling retained-engine provisioning, WAN latency/load and full backup/restore remain for integrated qualification. Seamless crossing/preloading and final art remain separate unfinished work.

Acceptance: the three-traveler network-host crossing passed in 40.1 seconds. Configuration validation and unrenewed assignment expiry passed; the initial host run exposed an overlong private Unix socket path, corrected to a short per-host sequence. Type/boundary/wire checks, both SSR service builds and Compose configuration validation passed. The furnished physical-GPU demo then crossed two accounts between two separately running host agents, reunited them, and returned a fresh browser to the same traveler from an old room link. Both agents reported one retained runtime. The destination screenshot was visually reviewed. All these agents ran on this machine; no physical multi-machine, TLS reverse-proxy or Docker execution claim is made by this record.

#Preloaded crossings in the player page

Doorway and menu crossings now prepare the destination before committing ownership. The coordinator checks invitations, resolves the authored route, warms the matching host and checks its arrival gate without freezing or moving the source traveler. A short-lived worker validates the destination's immutable manifest and prepares entry-place geometry, terrain and model/texture dependencies. Content bytes warm the browser's immutable HTTP cache. If preparation fails, no transfer is sent; the traveler stays in the source room and can close the panel or retry.

After the existing transfer journal commits, the player keeps the current document, canvas, controls and audio owner. It replaces the simulation/presentation from the destination's authoritative admission, verifies a rendered frame, and resumes without another Enter click. The URL and return credential follow the destination. Repeated crossings have their own bounded arrival attempts and do not consume the crash-recovery allowance. Reload still uses the existing journal/owner recovery.

For a fresh demo built from the current branch (existing demo rooms pin their old engine), run:

bash
DW_CROSSINGS_DATA_DIR=.data/preloaded-crossing-rooms npm run dev:remote-crossings

Open http://localhost:8799, use the printed private account-file paths to sign in, and press E at the brass doorway. A second browser/account can follow. Esc → Visit another room also uses preloading and automatic arrival. The data-directory option keeps existing demo travelers intact; restart this directory to keep this demo's saved state.

At this increment the public WebSocket reconnected to the destination; the continuous-socket follow-up below removes that reconnect on healthy crossings. The simulation worker is still replaced, and worker continuity and seamless portal views remain unfinished. The preload checks compiled content and warms bytes; destination native physics and GPU preparation still happen after transfer. HTTP cache eviction can require fetching assets again. A changed release, full destination or revoked invitation between preload and commit is rechecked by the journal/admission path. Broader device/low-memory, repeated graphics replacement, lost-response/fault and physical multi-machine TLS/Docker qualification remain open, along with the final character/environment art approval.

Focused acceptance: the remote three-traveler transfer/inventory check and the invited-account/cross-browser return check passed (46.8s and 23.8s), including preflight leaving the source owner/socket active, rejecting an invalid credential, and refusing an uninvited account. Code, boundary and wire checks and the packaged client build passed. In the physical-GPU browser, a deliberately failed manifest preload sent no transfer and retained the source traveler; the subsequent authored crossing kept the same document and automatically resumed.

The initial two-browser furnished-demo reunion was not accepted (resolved by the lightweight-snapshot follow-up below): the destination repeatedly returned its existing retryable admission-pressure refusal while another traveler was connected. No ownership transfer was attempted for the refused traveler. Both hosts later reported healthy after the clients closed. The refusal was kept as the next shared-play blocker; do not bypass the protection or count the browser reunion as passed. An intermediate test also attempted to click the HUD while pointer lock was active; the maintained keyboard test targets the game canvas explicitly.

The final single-traveler physical-GPU run passed three consecutive menu crossings in one document, preserving the actor and automatically resuming each time. The arrival screenshot was visually reviewed. This does not substitute for the blocked two-browser reunion or art approval.

#Lightweight saved-state snapshots for shared arrival

The furnished two-room refusal was reproduced with one connected traveler per host. Admission reported event-loop, with recent maximum delays of 356ms and 272ms against the unchanged 250ms limit; simulation ticks stayed below 9ms and memory/queue limits were healthy. Routine snapshots called the full release reader, which fetched, validated and hashed the immutable world, including its art assets, every four seconds even when the traveler had not moved.

The live host now reads only committed player/inventory/shared state for those snapshots. A single room lock still protects the view; each read checks the live owner, epoch and expected release. Startup and content changes retain full source validation, and writes retain their existing transaction checks. Admission thresholds remain unchanged; lease, release and transaction checks stay enforced. This removes repeated immutable asset processing from idle saved-state reads rather than hiding overload.

Focused checks passed for inventory equivalence, returned-value isolation, missing players, changed releases, expired/replaced leases, remote-host transfer/inventory and account recovery on another browser. The remote preflight check now uses the same bounded retry as the client for an explicit transient pressure refusal.

Physical-GPU acceptance: two invited accounts crossed into the same furnished room, automatically resumed and appeared as two connected travelers. Three consecutive crossings retained the original actor and document. The shared arrival was visually reviewed. The failed-manifest preload still left the source intact without sending a transfer. A later third-context startup did not become ready within 45 seconds. An isolated fresh browser then returned through the original invitation and rendered the current destination successfully. Wider simultaneous-browser/device/load qualification and the final art pass remain unfinished.

Use a fresh current-engine demo while preserving older retained-room saves:

bash
DW_CROSSINGS_DATA_DIR=.data/shared-arrival-rooms npm run dev:remote-crossings

#Continuous public connection across room hosts

Transfer-enabled coordinators now offer an opt-in continuous public WebSocket. The page owns it across simulation-worker replacement. After ownership commits, the coordinator retires the source private connection and waits for a fresh, single-use destination ticket for the same traveler. Only the new private room connection changes; the public connection stays open. Source movement queued after commit is discarded. Room/release/engine/epoch ticket checks and the destination's actor-bound hello remain enforced. Legacy clients retain the original route.

The destination worker waits for the private route to be ready before sending its hello. The page handles either ordering of the HTTP transfer response and socket handoff notification. The gateway bounds an unfinished handoff to 60 seconds; missing control, dropped connections and reloads use normal reconnect/journal recovery instead of claiming uninterrupted transport through a failure.

Focused checks: real PostgreSQL/local JSON and remote binary host journeys each kept one public socket through transfers, preserved inventory and rejected another traveler's otherwise valid ticket. The page/worker test covers the HTTP-first race and binary messages across the message port. Type, package-boundary and wire-catalog checks passed.

Run the current retained-engine demo without replacing earlier saves:

bash
DW_CROSSINGS_DATA_DIR=.data/continuous-crossing-rooms npm run dev:remote-crossings

Open http://localhost:8799 and sign in using a printed private account-file path. Press E at the doorway, or choose Esc → Visit another room; a second invited account can follow. The simulation worker is still replaced at arrival. Staged native physics/GPU preparation, worker continuity and seamless destination views remain next; network partitions, multi-machine TLS/Docker, wider load/device qualification and final character/environment art approval remain open.

Physical-GPU browser acceptance passed: two invited travelers crossed and reunited, each with one public connection. Three successive crossings kept the original traveler/document and recorded three handoffs on one socket. A failed manifest preload still sent no transfer. No page errors were reported. The milestone screenshot was visually reviewed; its two-traveler HUD confirms the shared destination. An initial harness attempt started before the coordinator listened and was rerun after startup completed. This is local network acceptance, not a multi-machine or seamless-rendering claim.

#Retained simulation worker and graphics owner at arrival

The crossing path now releases the source room inside the existing simulation worker, retains the graphics owner/device and presentation channels, and admits the destination into those owners. The source image remains available while the new room bootstraps. A verified destination frame and connected room are still required before play resumes. Actual worker/device failure keeps the existing replacement recovery path.

Release pauses source simulation, closes its room client after detaching that client from the public socket owner, cancels local travel/preloading, and disposes source physics/content. Destination startup resets room-specific movement, conversation and admission state. Presentation generations and outstanding-buffer acknowledgements survive the handoff so stale receipts cannot strand the channel. A source revision/cargo operation still in flight is refused with saved-destination recovery rather than mutating its resources concurrently.

This retains worker/device ownership; it does not yet stage destination native physics before the ownership commit or display the destination through the doorway. Those remain the next arrival work. Shared revision/cargo overlap, device loss mid-handoff, long-session memory and wider deployment/device qualification remain for the final pass. Character/environment approval is still required.

Use a new retained-engine directory to test this build:

bash
DW_CROSSINGS_DATA_DIR=.data/retained-arrival-rooms npm run dev:remote-crossings

Focused acceptance passed: the source-client detachment/message-port test, type/boundary/wire checks and production client build. In physical-GPU Chromium, two invited accounts crossed and reunited; three crossings retained exactly one simulation worker, one graphics generation and one public connection for the original traveler. Failed preloading sent no transfer, and no page errors were reported. The arrival screenshot was visually reviewed. The old-source error deferral ends when source release is acknowledged, so destination errors use arrival recovery instead of being mistaken for retired source errors. Fault injection of that path remains for qualification.

#Prepare native entry collision before transferring ownership

The live simulation worker now prepares the destination's immutable streamed content and native entry collision before the client sends the transfer request. The source simulation remains owned and usable during preparation. After the journal commits, destination admission supplies current progress; activation consumes the prepared native scene and applies those committed doors, inventory and player state. The retained worker, graphics owner and public socket remain in use. This replaces the separate asset-only preload worker.

A rejected preload or transfer cancels and disposes the stage. Stages match the destination content URL, content/manifest identities and physics backend, and can be consumed only once. Arrivals outside the prepared entry pose and route/cargo save profiles prepare their exact committed residency normally. Changed push-prop collision also rebuilds the native scene rather than using stale geometry. These paths preserve correctness but do not claim reuse of the prebuilt scene.

Focused native checks cover both physics backends: committed progress, checkpoint and movement equivalence, activation without another asset request, single-use ownership, cancellation cleanup and changed-residency fallback. Destination GPU uploads still happen at arrival; staging them and live views through a doorway remain unfinished. Wider concurrent revision/cargo, pressure, fault and device qualification and final art approval remain open.

bash
DW_CROSSINGS_DATA_DIR=.data/staged-arrival-rooms npm run dev:remote-crossings

Physical-GPU acceptance passed with two invited accounts: failed manifest preload sent no transfer; the request interceptor required native preparation to finish before each transfer; both arrivals reported consuming prebuilt collision. Three consecutive crossings retained worker, graphics generation, public connection and traveler identity. No page errors were reported. The shared arrival screenshot was visually reviewed. Type/boundary/wire checks and the production build also passed. Route/cargo reuse and changed-prop reuse remain optimizations to revisit with the qualification cases above; their exact-state fallback remains active.

#Prepare destination GPU resources before transfer

The room stage now also prepares the destination model catalog, surface/text resources and its existing within-world portal views before reporting readiness. The source presentation remains accepted. Ownership transfer is sent only after both native collision and GPU preparation complete; arrival commits the prepared catalog with a fresh baseline built from admitted destination state. The retained portal projection is rebound to the new simulation. Preparation never grants a second authoritative traveler or publishes preview progress.

Full GPU catalog revisions now accept a refreshed commit baseline under the same mesh, entity-budget, surface/text resource-key and advancing-generation checks as background revisions. A changed physics residency or graphics-owner generation aborts staged GPU resources and uses ordinary destination initialization. Cancellation also releases the staged catalog. During full catalog preparation the source image can remain frozen; this is not yet a live cross-room doorway view.

The stage temporarily builds a disposable preview simulation for its GPU baseline. Peak memory, concurrent content revisions, device loss and nested portal freshness need wider qualification. Cross-room destination views and final art approval remain open; the existing portal resources here are within-world views from the published world, not remote-player subscriptions through the new doorway.

bash
DW_CROSSINGS_DATA_DIR=.data/gpu-arrival-rooms npm run dev:remote-crossings

Focused acceptance: physical-GPU Chromium verified that physics and GPU readiness preceded transfer requests, both invited accounts consumed staged graphics, and three crossings retained worker, graphics generation, socket and traveler identity. A failed preload still sent no transfer; the shared destination was visually reviewed. The full-catalog renderer check accepted refreshed geometry, rejected an unknown mesh without consuming the accepted catalog, preserved the source on cancellation and released all owned buffers/textures on close. Code checks and the production client build passed.

The initial multiplayer run passed shared arrival and a return trip, then refused the next crossing with “The destination is resting”; the traveler stayed at the source. A fresh repeat run completed all three crossings without that refusal. Admission thresholds remain unchanged. Load/peak-memory behavior is a deferred qualification item, not a passed sustained-load claim.

#Live destination doorway views

Format 68 adds an optional roomWindow to an authored room crossing. In the editor, choose Room doorways → Show the destination through this doorway, then set its aperture and destination view plane. Save and publish the same release to both configured rooms. The SDK accepts the same field through addRoomCrossing; offset is relative to the crossing position and both yaw values use radians. The destination plane does not change the arrival position.

Nearby connected travelers can see destination entry-place scenery and public traveler motion before crossing. The coordinator verifies the source session, its authoritative distance to the authored doorway and destination invitation on each request. A view creates no player, arrival reservation or second authoritative owner. It can show a room that is full; entering still requires admission. The observer simulation projects remote actors without acquiring a local identity or saving their state. Closing the view releases its simulation and content.

bash
DW_CROSSINGS_DATA_DIR=.data/live-window-release npm run dev:remote-crossings

Open http://localhost:8799/ in two browser profiles, use the separate Rowan and Willow demo account files, and join room 1. Have one traveler press E at the brass arch to enter room 2. The traveler remaining in room 1 can see them move through the doorway, then press E to join them.

Review items: updates currently poll once per second; one visible window is supported per place, without nested remote-room views. Initial GPU preparation can freeze the source image. Automatic walk-through and camera/pose continuity are still pending. Animated destination physics, environment-clock alignment, concurrent revisions, multiple windows/viewers, permission-revocation races, long-session memory, device/host load and physical multi-machine deployment need qualification. A more compact editor layout and character/environment art still require the final review.

Focused acceptance: physical-GPU Chromium showed one remote traveler moving in the doorway while the viewer remained alone in the source, then crossed and reunited both travelers with the original worker and public connection. The live view was visually reviewed. Editor preview/save/reload/undo passed; the fractional destination-height input bug found in that check was fixed. Nine remote-actor checks passed, including observer checkpoint isolation. The separate-host test showed a full destination without joining or transferring, rejected wrong origins/credentials/non-window doorways, and retained exactly two player records. Account tests rejected missing and revoked invitations. Type/boundary/wire checks and the production build passed.

Early host-test attempts hit cold-admission pressure, overly fast retry rate limits and a test client expiring without heartbeats. Bounded paced retries and normal client heartbeats passed without changing production admission limits. Sustained load remains part of qualification.

#Opt-in walking entry and aligned arrival

Format 69 adds walkThrough: true to a room crossing that already has a roomWindow. In Room doorways, enable Walk through this doorway automatically. Existing windows keep their explicit E interaction. The walking reference uses a 2.8-metre-high aperture so a standing capsule clears its lower edge; the source and destination view planes also define the traversal transform.

The client detects front-to-back entry inside the aperture, pauses at the threshold and supplies its input boundary. The source host freezes and validates that boundary, aperture reach and standing pose, then computes the arrival from the authored transform. The destination checks collision and nearby floor before the existing durable ownership transaction commits. Retries reuse that transaction; clients cannot supply arbitrary arrival coordinates. E and menu crossings retain the destination's ordinary entry spawn.

The walking presentation keeps the scene visible, preserves camera pitch and an existing pointer lock, and changes to the authoritative destination position and facing with the destination graphics. A refused crossing keeps the source traveler; step away from the threshold before retrying automatic entry.

bash
DW_CROSSINGS_DATA_DIR=.data/walk-room-final npm run dev:remote-crossings

Open http://localhost:8799/, join room 1 with the separate demo accounts in two browser profiles, and walk forward through the brass arch without pressing E.

Still required for seamless traversal: move preparation earlier into approach, remove the threshold pause, retain held movement/velocity through admission, reuse staged native geometry at transformed poses, and qualify camera/frame timing on both renderer placements. An occupied arrival can still receive the existing admission-spacing offset. Walking currently resumes at rest, and a cold or busy room can hold the source image during preparation. Different floor heights, rotated apertures in live multiplayer, blocked/changed destination geometry, interrupted input boundaries, permission changes and both physics backends need wider integration qualification. These remain part of the engine objective.

Focused acceptance: eight aperture/transform/SDK/authoring checks passed. The separate-host test committed the transformed arrival and facing once, rejected a forged input boundary and retained one authoritative owner after a retry. Physical-GPU Chromium exercised walking entry without E, source-image presentation without a modal, retained workers/connections and a two-account reunion; the doorway and arrival were visually reviewed. The first browser path missed the aperture because admission had spaced the second traveler to the side; the test now walks to the opening. One second crossing was refused before preparation; its retry observed “The destination is resting” responses and then completed. Admission limits were not relaxed. These results do not establish pause-free or sustained-load traversal.

The editor walking toggle passed preview/save/reload/undo in physical-GPU Chromium. The finalized type/boundary/wire checks and production build passed.

#Prepare walking arrivals during approach

The live, authorized doorway view now starts one bounded destination stage while its walking doorway is nearby. It opens independent content ownership, prepares entry collision and stages the destination graphics using the existing read-only view. The source scene and its accepted window continue rendering while the new catalog, surface, lettering and portal resources wait for the handoff. Leaving the view or starting another graphics revision cancels the unclaimed stage; preparation failure leaves the ordinary crossing path available.

At entry the worker claims matching content/backend preparation, waits for any remaining upload, and commits against the authoritative admitted scene. A changed standing pose in the same non-terrain entry reuses native collision after a teleport; changed residency, terrain position, cargo and prop geometry retain exact-state fallback. Keyboard holds survive automatic walking admission, but keyup, blur and hidden-page release still clear input. The client does not restore a stale key snapshot after arrival.

bash
DW_CROSSINGS_DATA_DIR=.data/approach-room-final npm run dev:remote-crossings

Hold W while walking through the brass arch. Releasing W during admission should leave the traveler stationary on arrival. This reduces client preparation at the threshold; it does not remove the source freeze required by the existing server handoff. Admission pressure, network/transaction latency, physics velocity across ownership, controller/touch continuity, occupied-arrival spacing and physical multi-machine/device/fault qualification remain open. Initial window installation also still uses a foreground graphics revision. A traveler standing across the view plane can appear partially clipped in the remote window; straddling-body presentation needs review with seamless traversal. Final art remains required.

Focused native checks pass on Rapier and owned physics for committed doorway poses, movement agreement, no new content requests and single-use ownership; existing spawn and changed-residency/cancellation checks also pass. Physical-GPU Chromium confirms rendering continues while arrival resources are staged, invalid final geometry and cancellation preserve the accepted scene, and disposal releases owned buffers/textures. Existing graphics transaction checks pass.

The first live run exposed a reference-layout obstruction: a crossing can be captured just before the source plane, placing its capsule behind the destination plane. The old destination at z=6 left too little clearance from the back wall. Reference release lantern-house.crossings.5 moves the plane to z=5.5; focused standing-capsule arrival checks pass on both backends without relaxing collision or admission limits.

Live two-account acceptance exercised both input cases on physical-GPU Chromium: the first traveler released W during transfer and stayed still after arrival; the second held W and continued moving without another keydown. Both reused prepared native collision and graphics and retained one simulation worker. The approach view and two-traveler arrival were visually reviewed. The browser journey did not pass its socket-continuity assertion: the second crossing increased public connections from 1 to 2 with no routing-handoff control counted. Reconnection succeeded and the shared room showed both travelers. Keep this observed transport failure open for the next crossing increment; do not use this run as proof of socket continuity. Repeated initial admission-pressure responses also occurred; limits stayed unchanged. Type/boundary/wire checks and the production build passed. Physical multi-machine, long-session/fault/device and final art qualification remain deferred.

The socket-continuity follow-up is now repaired. The movement watchdog previously interpreted the host's intentional frozen input boundary as a stalled connection after two seconds. Crossing UI state now reaches the persistent client and holds that watchdog for the bounded transfer, without disabling transport heartbeats. Cancellation restarts ordinary movement-stall detection; the hold expires after 90 seconds and reconnects instead of waiting indefinitely. New input is not submitted to the frozen source.

Three real-socket checks cover pending acknowledgements during transfer, cancellation, deadline expiry and missing heartbeats. The complete physical-GPU two-account browser journey now passes: each traveler kept one public connection and one simulation worker, reused destination native/GPU preparation, and reunited; released and held W both behaved correctly. The continuous walking arrival was reviewed. Admission-pressure retries still occurred; admission limits were unchanged. Type/boundary/wire checks and the production build passed. The remaining threshold pause, velocity reset, straddling-body presentation, final art and wider qualification remain open. Next remove repeated full-world compilation from /_routing/transfer arrival collision validation by reusing the host's prepared simulation content, while retaining current collision/shared-state checks.

#Entry momentum and prepared host collision checks

Destination doorway validation now creates its disposable collision probe from the host's already compiled simulation content. Every probe still owns its ECS and native scene, applies current shared doors/props and public rigid bodies, checks obstruction and supporting floor, and disposes its mutable resources. The walking transfer path no longer calls Simulation.create to compile the same full world again at the threshold.

The source host retains velocity from its most recently applied input. A walking boundary can use that velocity only when its stream and sequence match the latest applied input; subsequent input invalidates the older boundary. This keeps idle braking during the HTTP handoff from erasing the entry impulse. Position, standing reach and aperture checks still use the frozen authoritative pose. The doorway transform rotates the velocity with position/facing, and the existing transfer journal commits it with the arrival pose. Fresh trusted motors restore saved contact velocity and grounded state for unmounted, non-ragdoll travelers; ordinary trusted reconnects also benefit from this restoration. Riding and ragdoll restoration retain their existing paths.

Fourteen native movement/doorway checks passed, including both physics backends. Additional shared-door probes verify that opening a door allows arrival and does not leak into the next closed-door probe. The real cross-host test sent a movement input, transferred its nonzero rotated velocity, checked the destination's first authoritative motor state, retried the operation and retained exactly one owner. Type/boundary/wire checks pass.

This is entry velocity continuity, not zero-latency traversal. Server journal and admission work still hold the source image. Arrival position can still advance while the source waits for its freeze; full airborne/swimming and held-jump continuity, occupied-arrival behavior, near-plane body clipping, physical multi-machine/device/fault/load checks and final art remain for qualification. Releasing W during admission now allows normal braking after arrival instead of forcing velocity to zero.

The physical-GPU two-account journey also passed. Both journaled arrivals carried nonzero velocity; released W braked to rest, held W continued moving, and each traveler retained one public socket and one simulation worker while reusing native and GPU staging. The shared arrival was visually reviewed. Initial join and destination admission-pressure retries still occurred; limits were unchanged. The production build passed. These checks prove the delivered walking path, not a pause-free, sustained-load or final-art gate.

bash
DW_CROSSINGS_DATA_DIR=.data/momentum-room-final npm run dev:remote-crossings

#Whole travelers at the doorway plane

Destination 3D travelers now keep one complete silhouette while any of their animated mesh bounds or attachments overlaps the destination half-space. Once all parts are outside, the renderer hides that traveler and its shadow together. The GPU still clips scenery per fragment, and source depth and the aperture continue to bound the destination image. This avoids floating heads and separated limbs when a traveler stops across the window plane. It is a window presentation rule; it does not create a second physical traveler or transfer ownership early.

Six focused portal geometry tests pass, including rotated planes, offset skin bounds and bodies wholly outside. The physical-GPU silhouette test matches the whole-body reference exactly in the common interior above the source step, sees the traveler change the image, and verifies exact image agreement for hidden outside travelers, clipped scenery and source-wall occlusion. The source step correctly occludes feet and is excluded from the translated-aperture reference. Type/boundary/wire checks and the production demo build pass.

The older all-in-one portal gallery stopped at its existing shared-native-handle census assertion and did not complete. That assertion remains a qualification follow-up; the focused test does not claim the complete gallery or device-recovery suite passed. This change covers 3D actor meshes; tile sprites, large crowds and additional animated/ragdoll/attachment combinations still need broader review. The frozen-camera transfer pause, multi-machine/fault/load checks and final character/environment art approval remain unfinished.

The real two-account journey passed released-W braking, held-W continuation, one public socket and one simulation worker per traveler, staged native/GPU reuse, and reunion in the destination. The doorway screenshot was visually reviewed: the waiting traveler now has a complete body at the plane. Join and destination admission-pressure retries occurred and recovered with the existing limits. Use a fresh current-engine demo:

bash
DW_CROSSINGS_DATA_DIR=.data/silhouette-room-final npm run dev:remote-crossings

Stopping this local demo with Ctrl-C reported a regional room shutdown failure; the host processes and PostgreSQL then stopped. Graceful multi-process shutdown remains a qualification follow-up; this run establishes the live crossing path.

#Looking during a walking handoff

Walking handoffs now accept mouse drag/pointer-lock and keyboard look input while movement awaits destination admission. Turns accumulate relative to the source view and are applied to the transformed arrival facing, with pitch retained. Refusal resumes the source with the chosen viewing direction. Gameplay actions remain suspended during the transaction; keyboard movement changes are retained for resumption. Controller look follows the same path, with physical-device and touch continuity still awaiting qualification.

A claimed, prepared destination no longer closes the source's live doorway view. Authorized preview polling continues until source release. Transient preview refusals retain the accepted view while a transfer is pending, and cold graphics preparation uses the existing background-arrival mode so it does not deliberately stop the source renderer. The source view is closed when ownership commits and room release begins.

This earlier increment removed locked viewing and premature view closure. The continuous-walking implementation below supersedes its movement hold when the approach preview is ready and supports destination collision prediction.

Focused physical-GPU acceptance passed in room-gateways.spec.ts: hold the real transfer request, turn with the keyboard while the camera position stays fixed, observe successful preview refreshes, refuse the first crossing and resume the source without losing the turn, then cross successfully with facing/pitch, one public socket and one simulation worker retained. The waiting view and arrival were visually reviewed. Production build and type/boundary/wire checks pass. The browser test directly exercises keyboard looking; mouse and controller device variations, multi-traveler motion during the hold, touch and continuous locomotion remain to qualify.

Run the focused check with DW_GPU_PROFILE=hardware, PLAYWRIGHT_EXECUTABLE_PATH=/usr/bin/chromium, DW_TEST_ROOM_GATEWAYS=1 and DW_TEST_CROSSINGS=1:

bash
node tools/cli/test-persistent.mjs --rooms --trusted --grep 'walking handoff keeps'

#Continuous walking through destination admission

A prepared walking doorway now uses its destination collision preview while the transfer request and destination admission finish. Walking, turning and releasing movement continue in the destination view. The source remains the sole owner until the transfer commits; interactions stay suspended. Refusal restores the source view and resumes the saved traveler. The temporary full-screen view uses the existing high portal tier, preserving the portal pixel/resource budget.

Movement is buffered at 30 Hz for at most 600 steps. The destination replays it through ordinary trusted movement, with the duration bounded by the source host's last accepted input time. Migration 14 journals that timestamp, the initial destination revision and an applied receipt. Pose and receipt commit together: a retried hello cannot apply the movement twice, and a later destination revision cannot be overwritten. Up to 12 additional steps during hello are submitted via the ordinary input window. The accepted baseline replaces prediction before the prepared destination graphics commit; the public socket, simulation worker and staged physics/graphics remain in use.

Focused acceptance: both physics backends match buffered replay against ordinary movement; database checks cover receipt races, time bounds, identity and later revisions. Real separate-host crossings cover unchanged entry momentum and a six-step buffered arrival, persisted pose and exactly one owner. The physical-GPU browser test holds the transfer request while walking at least 0.8 m, checks live previews and refusal recovery, then verifies arrival within 0.4 m of the transformed preview with retained direction, one public socket and one worker. Screenshots: walking during admission and arrival.

Qualification notes: arrival can make a small correction from prediction to the host's accepted entry pose. The temporary view remains bounded below full display resolution. Prediction stops after 20 seconds (or a full post-hello input window); unsupported/unprepared destination collision uses the authoritative hold. Live moving-body/platform prediction, held jump/airborne entry, rotated-door device input, multi-traveler movement during the hold, interrupted admission and physical multi-machine clock/network variation still need integrated coverage. These remain qualification items; final character/environment/vehicle art and full-plan acceptance are still open.

Focused commands (use the browser environment above):

bash
node --import tsx --test --test-name-pattern='handoff' tests/trusted-motion.test.ts
node tools/cli/test-control.mjs --test-name-pattern='handoff motion' tests/player-transfer.test.ts
node tools/cli/test-control.mjs --test-name-pattern='walking across hosts' tests/room-routing.test.ts
node tools/cli/test-persistent.mjs --rooms --trusted --grep 'walking handoff keeps'
End of Room routing