Shared World Multiplayer
Shared World connects the same loaded scene across Windows, macOS, iOS and web through a host and up to 15 clients. It shares selected entities, their transforms and animation, and small game messages. It requires Extended mode, matching game content and a configured HTTPS signaling service with a TURN relay.
The portable core, exported Chromium runtime and Linux native/browser interoperability have automated coverage. Windows, Apple SDK/device builds and the complete browser/network matrix still require release qualification; see the repository implementation plan before shipping.
Run the playground
- Open samples/shared-world/Shared_World_Playground.bb in Extended mode. Keep playground.world, avatar.x and crate.x beside it.
- Deploy the service and relay using services/shared-world/README.md from the source distribution. Put
b3d-shared-world.jsonbeside the game assets, containing{"service":"https://play.example.com"}with your actual service origin. This file contains no secret. Include it in the app/web asset export. - Run identical copies on both targets. Choose Host, copy the address, then Enter world. On the second copy choose Join with that address. Hosting and joining use the same source on every target.
- Move with WASD/arrows or hold the direction buttons. Push launches the falling crate, Door raises/lowers the authored door, and Reset replaces the crate. Space, E and R perform these actions from a keyboard. Leave or Escape exits and cleans up.
The sample includes working touch-sized controls, animated avatars, a roster, connection status and host-authoritative physics. The smaller runtime acceptance fixture in modern/network/tests exercises the same public commands in an exported game.
Start and update a session
Load a .world with LoadWorldAsync, wait for WorldReady, and check WorldNetworkFingerprint. Shared World rejects plain CreateWorld handles and loaded worlds without a valid fingerprint. Required model assets must be ready; optional streaming assets cannot act as shared prefabs.
Use StartSharedWorld for Quick Play, Host private and Join. Quick Play enters an available public game with matching game and world fingerprint, or starts one for others to join. Private rooms are never selected. For your own interface, MatchSharedWorld, HostSharedWorld and JoinSharedWorld return immediately after accepting setup. Pump updates and wait for SHARED_READY before spawning, then query SharedWorldRole. A zero return is an immediate failure; inspect SharedWorldError. Game identifiers are limited to 128 UTF-8 bytes, player names to 64 bytes.
Use SetWorld to select the bound world before UpdateWorld; networking runs before simulation and samples local state afterward. If you step a standalone host physics world, call StepPhysics before UpdateWorld. UpdateSharedWorld performs both phases for games without UpdateWorld or while another world is selected. Explicit and automatic calls share a frame token; Flip advances it, and a 50 ms time slice advances loops without Flip. Yield using the usual event/timer commands. All Shared World commands belong on the main runtime thread.
Own your player; let the host own the scene
SpawnSharedPlayer creates your sole character from a required model asset. On a client, its network registration completes asynchronously: a nonzero local handle can temporarily have SharedEntityID=0. Only drive input, animation and physics on locally owned entities. Other players are interpolated visual replicas.
The host uses SpawnSharedEntity for props. To share an existing authored door, call ShareWorldEntity(door,""). Peers bind their own matching WorldEntityID rather than duplicate it. Dynamic instances may be roots or children of an authored parent; changing shared parenting during the session is rejected.
Position and quaternion travel at up to 20 Hz, with 100 ms interpolation and at most 100 ms extrapolation. Change thresholds are 0.001 world units and 0.1 degrees; scale changes of 0.001 travel reliably. Settlement samples stop extrapolation when motion stops, and full pose refreshes heal packet loss. A 16 KiB state budget per peer per tick prioritizes players and overdue entities; large moving populations update less frequently. The 4096 entity capacity is not a promise that 4096 entities all move at 20 Hz.
Root Animator mode, clip, time, speed and transition replicate, with periodic playback corrections. Peers evaluate the same local animation asset; individual bone poses are not transmitted. Remote animation events, MD2 animation and bone-root-motion configurations are outside this contract. Playback and pose on borrowed authored objects are restored on stop.
Physics and game actions
The authority simulates each physical entity. Remote shared entities skip local collision response and dynamic physics; existing managed bodies are suspended and restored when sharing ends. Do not create standalone remote bodies or joints. The sample creates its standalone crate body only on the host and shares its resulting transform. Contacts, velocities, joints, constraints and deterministic simulation are not replicated.
Use SendSharedMsg to request game actions. The host receives a client's message and relays it to the other clients; the sender gets no echo. A host message broadcasts to clients. Validate the sender and action in your game before applying it. The optional entity must belong to the sender, so a client refers to its character when requesting a push, not to the host-owned crate. Types are 1–65535; data is at most 4096 UTF-8 bytes.
Drain both RecvSharedMsg and RecvSharedEvent every frame. Each queue holds at most 1024 entries with a shared 2 MiB byte bound; overflowing fails the session rather than growing memory indefinitely. Initial baseline entities and players are queried once READY; change events are not a replay of the baseline. Message/event entity queries resolve live handles and return zero after deletion.
Leaving, deletion and interruptions
StopSharedWorld removes created remote replicas, restores borrowed authored presentation and keeps local entities and the world. UnshareEntity retains the local entity too. FreeEntity on an authoritative shared entity reliably removes it on peers; freeing a remote shared entity is rejected.
Host deletion of an authored subtree is permanent for the loaded world, includes its ownership closure, and is sent to late joiners. It emits one ENTITY_DESPAWNED event with the authored root ID in SharedEventData. Stop cannot undo this deletion: reload the world before hosting a fresh session.
Automatic and manual host migration
Host migration is enabled for every new room. If the host leaves, crashes, or stops renewing its runtime lease, the service chooses the oldest ready surviving member. The room address and surviving player IDs remain stable. World entities pass to the new host; a departed host's character is removed. Existing local characters and surviving replicas are rebound in place. A room with no ready successor ends.
The host can call TransferSharedWorldHost(player) to pass authority explicitly while staying in the room. The successor acknowledges a reliable checkpoint before authority changes. The service rotates the host credential, advances the generation and reconnects the WebRTC star. Stale-generation callbacks and packets cannot modify the replacement session.
SHARED_EVENT_HOST_MIGRATING enters SHARED_MIGRATING; continue UpdateWorld or UpdateSharedWorld and drain events, while pausing game actions that require sharing. SHARED_EVENT_HOST_CHANGED returns to READY and identifies the current host. A timed-out or cancelled manual attempt resumes the original host. Query SharedWorldRole each frame rather than caching the initial result. SharedHostPlayer identifies the authority; it is not always player 1.
Automatic recovery uses the successor's last replicated world state, so recent in-flight changes and messages can be lost. If permanent authored deletion conflicts with that checkpoint, the affected peer fails safely and must reload the world. Arbitrary game variables, script execution, standalone physics, contacts and velocities are not transferred. Replicate application state separately. Release standalone world bodies before manual transfer and recreate them on promotion; the playground's Pass host button demonstrates this and resumes physics at the replicated pose.
Recovery requires the room service and a ready successor. It is not service failover, seamless reconnect after restarting a game, or a guarantee across a network partition. Host runtime leases expire after 15 seconds and are checked by the service sweep; reconnection has a 30-second deadline. An unrecoverable interruption emits SESSION_LOST and freezes remote presentation until StopSharedWorld. Entity IDs are never reused across generations: each host generation can allocate up to 1,048,575 entities over its lifetime (4096 live), and a room supports 2047 generations.
A signaling outage invalidates the join address but preserves established WebRTC data channels. SharedWorldError explains that new joins are unavailable. Relay failure can still interrupt gameplay; fresh TURN credentials are requested every five minutes while signaling remains available.
Deployment and classic networking
Serve browser exports over HTTPS, allow the WSS service in connect-src, and allow the exact game or preview origin on the service. A preview must retain a real origin; a sandboxed opaque origin is rejected. Do not disable TLS certificate checks or package TURN secrets. Validate both directory and single-file exports with the same world bytes.
Classic StartNetGame, HostNetGame and JoinNetGame retain their existing native behavior and do not form Shared World rooms. A process may run one networking system at a time. Shared World is the cross-target API; it does not automatically turn a single-player simulation into a shared game.