# Lantern passage Lantern passage is an early shared playtest for carrying connected cedar decks and their riders between two places. A timber and brass gate looks from the morning quay into the evening garden. Both decks retain their identities and hinge; the destination starts without a duplicate assembly. ## Automatic moving-object crossings (F65) Gateway windows now offer **Let moving objects pass through**. Enable the window and this option in the gateway inspector, then Preview, Save and Apply. Push an unoccupied body toward the front of the opening: crossing its plane transfers the body/connected island automatically. No cargo Interact request is needed. The opening must fit the complete island. Ordinary saved gateways keep their existing behavior until this option is enabled (`window.bodies: true`, cargo format 31+). The room authority detects the swept center crossing, checks the aperture and arrival, and transforms position, orientation, linear and angular velocity with the portal's rigid frame. Body membership and native state persist in the ordinary room checkpoint before publication. Clients consume that membership through the existing shared frame. Source/destination model assets are admitted ahead of time for these worlds. Process/network place owners defer the crossing decision to the coordinator after their step results are combined. Trusted game hosts can launch a free body as a projectile with `RoomFloating.launch(id, velocity, angularVelocity)`. Velocity components are bounded to 80 m/s; linked, pinned, planar, vehicle and ragdoll bodies cannot use that launch entrypoint. Player pushes retain their ordinary authority checks. At most four unoccupied islands cross in one room batch. A closed requirement, undersized aperture or obstructed arrival retains the body at its previous safe source pose. Occupied decks retain coordinated cargo transfer; cross-room vehicle transfer is the separate F66 feature. Focused smoke: both native backends transfer a 30 m/s projectile, project its new membership into the matching simulation replica, and continue a restored checkpoint exactly. Type/boundary/wire checks pass. Wider CCD/rotating-aperture, network-owner fault, rendered-device and load qualification are deferred until feature completion. No new screenshot was captured for this native-state increment. ## Try it in the editor Restart your editor terminal after pulling `codex/engine-foundations`, then run: ```sh npm ci npm run dev:editor ``` Open the private editor link printed in the terminal. Choose **New world → Lantern passage → Preview → Save**, then **Apply** to the shared development room. Open **Join the room** in two browser profiles. Gateway labels/windows and floating body settings support saved Undo/Redo and live Apply. Walking through a gateway uses its arrival point; crossing aboard a deck carries its connected assembly. ## Try a persistent room Use the PostgreSQL connection setup in [Persistent rooms](PERSISTENT_ROOMS.md). Choose a fresh room ID so the test uses the new engine and world: ```sh DW_ROOM_TEMPLATE=cargo DW_PERSISTENT_ROOM=lantern-passage-test npm run dev:persistent -- --rebuild ``` For server-authoritative movement, use the same command with `dev:trusted` and a separate fresh room ID. Keep your configured public origin, port and database settings. An existing room retains its pinned release, even after rebuilding. Open the room in two browser profiles. Walk along the jetty and onto the first cedar deck, with one traveler on each side. Continue toward the gate until the prompt says **Carry the decks to evening**, then press **E**. Wait for both travelers to finish loading. Both riders and the linked decks cross together. Use **Carry the decks to morning** to return. Reload a traveler or restart the room to check that the accepted destination and cargo remain together. ## Separate process owners An opt-in persistent runtime now assigns each place to a separate physics process. Connected cargo moves from the source process to the destination process through prepare, durable commit and activation. PostgreSQL records each place owner and its epoch with the cargo/rider receipt. Ordinary physics saves from an older epoch are rejected. The room coordinator publishes only committed combined snapshots. Use a **new dedicated PostgreSQL database** for this engine: migration 7 adds the owner registry, and existing retained engines do not accept newer schemas. A new room ID in an older occupied database is insufficient. With that database selected: ```sh DW_PHYSICS_OWNERS=process DW_ROOM_TEMPLATE=cargo DW_PERSISTENT_ROOM=lantern-process-test npm run dev:persistent -- --rebuild ``` Keep `DW_PHYSICS_OWNERS=process` when restarting that room. Its recorded owners are replaced with new identities and higher epochs; the saved cargo location stays unchanged. `/health` reports place owner IDs, epochs and process IDs. The existing Docker engine package includes the owner executable; set the same environment variable for a fresh persistent deployment. This first implementation uses private local IPC and up to eight place processes. It provides separate simulation owners on one machine. Authenticated network transport is available below. Independent domain failover and capacity qualification remain open. A failed child fails the room; its supervisor restores the committed state. ## Network owners and Coolify `compose.cargo.yaml` is a fresh deployment with PostgreSQL, one room coordinator and two private physics services. In Coolify choose the Docker Compose build pack, set the file to **/compose.cargo.yaml**, and assign your HTTPS domain to **room**, port **8793**. Set these environment variables: - `DW_ALLOWED_ORIGINS`: the exact public HTTPS origin. - `DW_ROOM_PASSWORD`: the private traveler passphrase. - `DW_POSTGRES_PASSWORD`: a private database password. - `DW_PHYSICS_TOKEN`: a separate shared worker credential, 32–256 characters. Compose creates the named database and retained-engine volumes. Workers are stateless and have no public ports. The private Docker network explicitly permits unencrypted WebSockets; the worker credential stays server-side. The browser uses the same room invitation and crossing controls as before. This repository includes the configuration; no production deployment has been performed. For workers on separate machines, run the **same packaged engine version** on both sides. After building with `npm run build`, launch each worker with: ```sh DW_RELAY_PORT=8795 DW_PHYSICS_TOKEN='' npm run physics ``` Expose it through a trusted HTTPS/WSS reverse proxy. Configure the room with: ```sh DW_PHYSICS_OWNERS=network DW_PHYSICS_ENDPOINTS=wss://physics-a.example/owner,wss://physics-b.example/owner DW_PHYSICS_TOKEN= ``` Endpoints are assigned to places in order, repeating if there are fewer services than places. A worker service allows eight owners by default; configure `DW_PHYSICS_CAPACITY` to change its bounded capacity (1–32). For an explicitly private non-TLS network, set `DW_PHYSICS_ALLOW_INSECURE=1` on the room. Loopback connections are allowed for local testing without that override. The handshake checks the exact worker executable hash before sending world data. A room pinned to an older engine therefore needs workers from that retained bundle, not workers rebuilt from a newer checkout. Start that bundle's `runtime/physics-service.mjs` directly. Connections have bounded messages, one request in flight, timeouts and idle retirement. Disconnecting the coordinator retires its worker; reconnecting creates a fresh owner under a newer database epoch. The focused network check passes through two separate TCP services, carrying two travelers and leaving the observer behind, then restarts the room and checks the new owners. Authentication, capacity, executable mismatch, disconnected-worker retirement, type/import checks, packaging and a packaged-service socket smoke check pass. Compose configuration validates. These checks ran on one machine; real WAN/TLS proxy behavior and a live Coolify deployment remain unqualified. ## Transfer behavior Nearby clients acknowledge their final positions or trusted input boundaries, then prepare destination collision, models and graphics. Automatic movement saves finish before the freeze acknowledgement. The source and destination physics wait while other places can continue simulating. The room checks support, aperture, destination clearance, portal access and player revisions before committing body membership and every rider in one PostgreSQL transaction. The editor relay saves its native checkpoint, affected traveler poses and crossing receipt together in one fsynced room snapshot before publishing the destination. Loading failure or a disconnect before commit keeps the source. A lost commit acknowledgement stops the old host; the next owner and reconnecting clients recover the committed destination. Graphics retain their transport ownership across the swap. A slow client that cannot publish the accepted destination reconnects to it. ## Current limits - Cargo uses world format 31 and the existing bounded dynamic-box/hinge adapter. Assemblies attached to a fixed world hinge must be detached before crossing. - Offline saved riders still aboard prevent a crossing. Online travelers who have stepped off are reconciled at the acknowledged freeze boundary. - Default transport runs within one room process. The opt-in process-owner mode transfers between separate place simulators under one durable coordinator; real remote-machine deployment qualification remains open. - Final character/environment art, broader engine performance qualification and full-plan acceptance remain outstanding. The relay retains up to 4,096 crossing receipts, then asks for a new playtest room. The isolated browser runner is `node tools/cli/test-persistent.mjs --cargo`; add `--trusted` for authoritative movement. It builds and launches its own test room and database, then removes them when finished. ## Verification Two real browser clients, one using Worker graphics and one using main-thread graphics, pass the outbound crossing and persistent-room restart on physical Intel WebGPU in cooperative mode. The trusted physical-WebGPU and cooperative software WebGPU journeys also pass destination page reload and the shared return crossing. Six focused native/network/presentation/source checks and eleven PostgreSQL cargo checks pass after the browser fixes; type and import boundaries also pass. The reviewed [quay capture](evidence/lantern-passage.png) shows the evening garden through the morning gate. This is early scene acceptance, not final art approval. Use `npm run test:browser:persistent-cargo` or `npm run test:browser:trusted-cargo` for the isolated browser journey. ## Editor delivery check The editor journey passes on software WebGPU with two real browser clients using Worker and main-thread graphics: create, gateway edit, saved Undo/Redo, shared crossing, live density edit, destination reload and return. It passed on physical WebGPU before the admission optimization; repeating that device pass is deferred. Type/import checks pass. The final focused run passes **22 of 23 checks**. The pre-commit disconnect/reconnect scenario fails with **“Cargo rider has no destination contact”** during preparation; keep that case open. Roundtrip, post-commit disconnect, empty-world recovery and live-revision checks pass. The gateway controls and returned scene were visually reviewed. The returned deck can be obscured by the portal view despite grounded collision; visual correction is deferred, and this does not constitute final art acceptance. ## Deferred review — finish-first delivery These are open work, not release qualification: - Investigate the recorded pre-commit disconnect recovery failure; verify the supported pose is admitted against the correct native boundary. - Qualify remote owners across real machines, TLS proxies and network partitions. Add domain-only recovery without restarting the entire room; current recovery deliberately replaces the whole room owner. - Run trusted-player, lost-acknowledgement, child-kill and concurrent-transfer qualification for local and network owners. The focused cooperative journey passes. - Measure aggregate child memory and sustained stepping cost. The current simple implementation restores a complete checkpoint per owner step; optimize after functional delivery, without weakening ownership checks. - Run the broader native/PostgreSQL regression suite and repeat final hardware and software editor journeys during the qualification pass. Earlier scoped passes do not qualify every subsequent change. - Exercise body addition/removal and gateway edits while cargo resides in the other place; inspect saved traveler fallback after incompatible edits. - Review long sessions, large assemblies, slow clients and crossing-history limits. - Review deck visibility through portal apertures and arrival transitions. Complete the required character/environment art pass with user approval. Editor/relay integration includes atomic crossing snapshots, simultaneous requests, failed preparation, disconnect/reconnect, restart recovery and live property edits. Admission now restores the accepted cargo state before its first graphics bootstrap; previously two consecutive preparations could exceed the existing startup deadline. ### Process-owner delivery check A focused PostgreSQL journey uses two real physics subprocesses, two carried travelers and a portal observer. It covers failed destination preparation, waiting for every traveler, one durable crossing, source retirement, distinct process IDs, epoch advancement, rejection of an old owner snapshot, and restart with replacement owners and preserved cargo. The pre-existing atomic cargo transaction check also covers rollback/retry and lost acknowledgement. Both checks pass. Type/import checks and engine packaging pass; the packaged owner executable separately restores and advances the linked bodies. Browser/device repeats are deferred because this increment changes server ownership and retains the delivered client workflow. ### Separate-owner recording and replay New authored-body takes preserve the starting physics owner IDs/epochs and each committed physics save's ownership. Ledger replay seeds an isolated temporary SQL ledger, reexecutes the crossing, checks epoch advancement and fenced saves, and checks that a retry does not advance ownership again. Run a new recording with: ```sh DW_REPLAY_DATABASE_URL= npm run replay:ledger -- ``` Focused acceptance: the same three-traveler live journey passed with inline, subprocess and local network-service owners on Rapier, including two canceled attempts and a completed crossing. Altered epochs were rejected. Source room and player rows and the source transaction count remained unchanged. Capture-schema and spawned-tail compatibility checks and type/boundary/wire checks passed. Physical-GPU studio playback passed in 5.2 seconds; the test follows an actual recorded rider rather than assuming a numbered traveler is on the cargo. [Reviewed screenshot](evidence/process-ledger-replay.png) shows native playback; SQL transaction replay is the separate command above. Deferred: interrupted/restarted owner histories, long mixed histories, real cross-machine service qualification and owned-backend live process replay. Existing takes without ownership fields remain readable but do not acquire missing ownership proof. This closes continuous authored-body owner replay only; it does not remove the single-continuous-segment ledger requirement.