Shared World connects the same loaded scene across Windows, macOS, iOS and web through a host and up to 15 clients, or a server room for up to 128 players. It shares selected entities, their transforms and animation, and small game messages. It requires 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.
b3d-shared-world.json beside 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.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.
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.
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. SharedWorldBytesSent and SharedWorldBytesReceived report the traffic each second, so you can see what a moving population costs.
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.
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.
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.
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.
A server room is a room whose host is a server: a copy of your game with no player of its own. It runs every player's entity, bots included, so up to 128 players can bump into each other with nobody's machine in charge. Players send their input, not their positions, and the server sends everyone the result. On Windows today you run the server yourself; hosted server rooms come later.
Start a copy with B3D_SHARED_SERVER=1 in its environment and HostSharedWorld (or StartSharedWorld, or MatchSharedWorld) serves a server room. That copy shows no window, RenderWorld draws nothing, and Flip (or FlipCanvas on the canvas that Graphics3DCanvas opened 3D graphics on) waits for the next of 60 ticks a second, so your ordinary game loop becomes the server's. SharedWorldRole() is SHARED_SERVER there and SharedLocalPlayer() is 0. Players join with JoinSharedWorld as usual; a client older than server rooms is told it can't join one.
Hosted server rooms will run your program on game servers as WebAssembly, the way a web export runs. There it reads the files you export with it, and what it writes lasts only as long as the room. It reaches no machine but its players': HttpRequest fails, and TCP and UDP streams can't be opened. Sounds play silently, and CreateWindow is a runtime error. A program that goes 10 seconds without calling Flip, or another command that waits, ends its room.
Before starting the session, every copy defines the same input actions in the same order: DefineSharedStick "steer", "wasd; arrows; left-stick" and DefineSharedButton "jump", "space; south". A client sends its actions to the server about 30 times a second while they change. A game with its own input code feeds them with SetSharedStick and SetSharedButton. On a touch screen (iOS, Android, or a web page on a phone or a tablet) the actions with keys also get touch controls of their own, sticks on the left and buttons on the right, unless the program makes its own with CreateVirtualGamepad or the other virtual controls commands. On the server, SharedStickX, SharedStickY, SharedButtonDown and SharedButtonHit read each player's actions; press counts travel with every frame, so a lost packet loses no press. With no session the same commands read your own keys for player 0, so one movement routine serves single player and server alike.
When a player joins, the server gets SHARED_EVENT_PLAYER_JOINED, spawns or shares an entity for them and gives it to them with SetSharedPlayerEntity; every copy then finds it with SharedPlayerEntity. SharedPlayerDriven(player) tells your movement code whether this copy moves that player's entity: always on the server, and on a client for its own player while it predicts them (below). AddSharedBot adds a bot, a player with no connection whose actions you set with SetSharedBotStick and SetSharedBotButton, so the code that moves people moves bots too. SharedTick gives the server's tick on every copy.
Without prediction, your own marble moves a round trip after you press a key, plus the wait for the server's next update: about a tenth of a second on one computer, and more across the internet. SetSharedPlayerEntity player, entity, True turns prediction on for that player, and an entity with a Marble or Walker behaviour has it on unless you say False. The player's copy then moves the entity at once, from its own input, with the same movement code (SharedPlayerDriven is True for it there) or the same behaviour. It sends the server its input for each UpdateWorld, and the server applies them one a step, as the copy did; each update then says where the server had the entity after one of them, and the copy corrects the difference. Velocity changes at once, and position and rotation slide in over about a fifth of a second, or snap past 5 units. Other players' copies of bodied objects become solid stand-ins where they are now, so bumping into someone happens at once, about where the server has it; being bumped by someone else still shows a round trip late. A copy of one of the world's objects, shared as "entity:" and its ID, gets that object's body and behaviours on the player's copy; give a model's copy what your code needs once SharedPlayerDriven turns True. Prediction suits one UpdateWorld for each 60th of a second, as the server steps.
Each player gets the room's entities in a compact form, about a third of the bytes, with the things nearest the entity they play as most often, within a share of about 48 KB a second. In a big world, SharedRoomRange 120 leaves out what is more than 120 units from a player's entity: their copy hides it until it comes back. SharedEntityRange gives one entity a range of its own, and a range of 0 means everywhere, for the ball or the flag that every player must always see.
The server keeps the room's rules too. SharedRoomPlayers 100 lets a room take 100 players, people and bots, and turns newcomers away beyond them. SharedRoomBots 20 keeps the room at 20 players with bots of its own, and one gives way each time a person joins. SharedRoomOpen False turns new players away during a round, and SharedRoomOpen True lets them in again. A player who gives no input for three minutes is told they were away too long and leaves; SharedRoomIdle changes the time.
When a player's connection drops, the room keeps their place, their entity and their input, at rest, for 30 seconds, and everyone gets SHARED_EVENT_PLAYER_AWAY. Their own copy rejoins by itself, showing SHARED_MIGRATING meanwhile, and when it is back everyone gets SHARED_EVENT_PLAYER_RETURNED and the player carries on with their own entity. After 30 seconds the player has left, with SHARED_EVENT_PLAYER_LEFT as usual.
A level your program builds from code, such as moving platforms, exists on every copy already. Call ShareKeyedEntity platform, "platform-3" on every copy, before joining or after: the server's platform is shared and moved by your code there, and each player's platform with the same key follows it. On a player's copy SharedEntityRemote(platform) is True, so your movement code leaves it alone.
Players can each be a copy of one of the world's own objects. On the server, copy it with CopyEntity, which brings its physics body and behaviours, and share the copy with ShareWorldEntity copy, "entity:marble": every player's world copies how the marble looks, as the replica. The World Editor's Marble and Walker behaviours move such a copy by its player's actions move, jump and view, with view the flat way the player's camera looks, which a client sets with SetSharedStick; a bot's copy follows a simple brain, and each player's own copy predicts their own. The World Editor's Online section does all of this with no code, its Play Online Test runs the server and a player on your own computer through the Shared World service, and samples/marble-arena is a whole game made with these commands.
A server room keeps players' messages to itself rather than relaying them, never changes host, and ends when its server leaves. SharedHostPlayer is 0 there, the server owns every entity, and player lists leave the server out. SpawnSharedPlayer and SharePlayerEntity are refused: the server gives players their entities.
A room's key says what it plays, such as a level. MatchSharedWorld world, "Super Gerball", name$, "garden/four_corners" puts players who pick the same level together, and a busy level simply opens more rooms. Every copy reads SharedRoomKey() and builds that level; the server has its room's key before the room is open. A room started without a key takes the key of the first player who joins it with one, so an online test's room plays the level its player picks, and a friend who joins by address plays the room's level, whatever they picked.
A game can offer new choices every day: list them in b3d-shared-world.json beside the service, one list for each choice, such as an easy, a medium and a hard level: {"service": "https://sw.blitz3d.plus", "daily": [["garden/1", "garden/2"], ["kitchen/1", "kitchen/2"], ["attic/1"]], "board": "lowest"}. The day starts at midnight UTC, and day number d takes the key at d modulo each list's length, so every player everywhere gets the same choices and no player's clock decides them. Once a day's choices are known they stay, so a new version of the game changes only the days after. Quick Play then takes only today's keys, and a room started yesterday plays on until it empties but takes nobody new. SharedDayLeft() gives the seconds until the next day.
QuerySharedGame("Super Gerball") asks the service, in the background, for today's and yesterday's choices, their boards and how many people are in rooms with each key. Call SharedGameReady() each frame until it gives 1 (or -1, when SharedWorldError() says why), then read SharedDailyCount() and SharedDailyKey(i), SharedKeyPlayers(key$), and each board with SharedBoardCount, SharedBoardName and SharedBoardScore: the best ten of today's, and the best three of yesterday's with day -1. SharedBoardPlace(key$) is this player's place on today's board.
Only a room's server posts scores, so nobody can post a faked one: when a player reaches the goal, SubmitSharedScore SharedRoomKey(), player, time#. Each player keeps their best of the day on each board, and their scores on one board are at least 5 seconds apart; "board": "lowest" in b3d-shared-world.json makes the lowest score best, and otherwise the highest is. Players are known by an identity their copy keeps for the game, so their best stays theirs from one run to the next, and by the name they play under, cut to 24 characters. A score goes to the boards of the day its room started, and boards are kept for 30 days. Only rooms the game servers run post to your game's boards: an online test's room keeps its own choices, from your b3d-shared-world.json, and its own board while it lasts, which its players' QuerySharedGame asks for.
In the Windows and Mac IDEs and the online IDE, Program > Run online test... runs your program as a server room on your own computer, with no window, and joins it as a player once the room is open, through the Shared World service. The online IDE runs the server in a part of the page no one sees, and you play in its program pane. Nothing is spent and no account is needed. It needs a program that starts Shared World, such as StartSharedWorld: that call serves the room on the server and joins it on each player's copy, whatever the program asks for. The program's command line goes to every copy.
Before it runs, choose:
The console shows what the server prints, after Server:, and what the other players print, after their number. The test ends when the first player ends. The IDE remembers your choices for next time. In the World Editor, Play > Play Online Test does the same for a world whose Online section is on, with one player and the world's own bots, and Blip can start either with more players, other bots or a bad network.
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.