← Paracosm·Docs/Handbook
Contents · WorldBridge

WorldBridge: the control API

WorldBridge (worldbridge/WorldBridgeModule.cs, with Hub.cs, Roam.cs and RoamNotecard.cs) is a non-shared OpenSimulator region module that opens an HTTP control channel into the live regions of its simulator. Everything in tools/ goes through it, via tools/wb.py.

The channel

Several regions

One simulator can run several regions (one Regions.ini section each; config.md says how to add one). OpenSim makes one WorldBridge instance per region, and they share ONE listener on the one port (worldbridge/Hub.cs): the first region to finish loading opens it, and it closes when the last region is removed.

The request queue

Every request is admitted by one accept thread (method, token, rate limit, role, body size, region) and then waits in one of two bounded lanes, each served by a small fixed pool of worker threads (worldbridge/Hub.cs):

lane commands workers may wait
read state, list, heightmap, avatars, chat, inspect, worn, contents, anims, raycast, and roam with status: true ReadWorkers (2) ReadQueueLimit (256)
work everything else MaxConcurrent (1) QueueLimit (64)

tools/wb.py:

from wb import wb, region_say
wb("state")
wb("spawn", x=128.0, y=128.0, z=25.0, shape="box", sx=1, sy=1, sz=1, r=1, g=0, b=0)
wb("set_script", uuid=u, name="flicker", script=lsl)   # raises ScriptCompileError on a compile failure
region_say(-40777, "on")                                 # talk to scripts' llListen (see scripts.md)
wb("list", region="Isle2")                               # a simulator with several regions: name one
region_say(-40777, "on", region="Isle2")

CLI: python tools/wb.py <command> key=value ... (values are parsed as JSON when they can be).

Endpoints

Positions are region metres. Colours and alpha are 0..1. "uuid" is an object (scene object group) id.

Reading the world (observer)

command body returns / notes
state none region name and UUID, root avatar count, object count, sizeX/sizeY (metres); regions, every region the channel serves (see "Several regions"); queue, the request queue's counts (see "The request queue").
list none every object: uuid, name, shape, x/y/z, sx/sy/sz, r/g/b/a (default face), rx/ry/rz/rw. Capped at 5000 objects. shape is the root prim's DESCRIPTION (spawn writes the primitive kind there), which makes it a free readback channel for scripts that write llSetObjectDesc.
heightmap optional grid (samples along the longer side, default 64, 8..512) point samples of terrain heights: 64 x 64 at step 4 on a 256 region by default. Returns width, height, stepX, stepY, samplesX, samplesY, grid.
avatars none per root avatar: name, uuid, npc, pos, headingDeg (0 = +X east, 90 = +Y north) and compass, camera position, camera lookAt.
chat limit (default 50) the buffered local chat avatars typed (ring of 300; each with time, from, channel, message, position). Object llSay is not captured.
inspect uuid the root prim's live render state: per face texture, materialID, rgba, bump, shiny, fullbright; sculpt type and mesh asset; the TextureEntry as hex; scale; and the LIVE sound (asset, gain, flags where 1 = looping, radius). Glow and light are not reported.
worn avatar (uuid or name; default the one visitor present, 400 if several) the avatar's attachments: name, uuid, attachment point.
contents uuid what the object holds: its ROOT prim's contents, sorted by name. Returns name (the object's), count, items: per item name, type (object, notecard, script, animation, sound, texture, bodypart, clothing, gesture, landmark, mesh, else the asset type in lower case), item (item id), asset, description (a bundled object's reads "a copy kept by Gaia (N prims)"), copy (whether the owner may copy it) and perms {owner, group, everyone, next} (the permission masks as numbers).
roam_notecard uuid, full (default false) the "roam" notecard a registered roamer would carry (see "A creature that carries its own roam" below): its saved roam config without the keys that belong to this world (uuid, cx, cy, water, leader; wary_of and hunts name kinds, so they stay), or with full: true keeping all of them but the uuid (for an island saved whole: an OAR keeps its objects' uuids), with every animation uuid the object holds in its root prim named by that item's name (one it does not hold stays a uuid). Returns name, notecard ("roam"), text (the JSON to write with put_notecard, \n line ends), config (the same, parsed), named ("key: name" per animation named), unnamed ("key: uuid" per animation left as a uuid), roaming (whether it is moving now). 404 for an object that is not a registered roamer (or one turned off).

Two more read-only commands need gaia:

command body returns / notes
anims uuid the object animations an object is playing, as the server holds them: anim (asset), seq, name.
raycast x,y,z (origin, default the region's middle at z 30), dx,dy,dz (direction, default +X), length (default 20, max 1000), count (max hits, default 4), land (default false) casts a ray through the physics engine: each hit's point, distance, and the owning object's name and uuid (land, or ? for a body no object owns). See physics-and-walkability.md.

Building (builder, then gaia)

command body notes
spawn x,y,z (required); shape box/cylinder/sphere; sx,sy,sz (clamped 0.01..64); r,g,b,a; name; rx,ry,rz,rw (quaternion); fullbright; glow; shiny 0..3; light with light_radius (max 20), light_falloff, light_intensity, light_cutoff (light colour = the prim colour when one is given) builder role. Owned by the estate owner.
recolor uuid, r,g,b,a builder role. All faces.
rez_item box (uuid), item (exact name, or the item id), x,y (required, clamped to the region); z (the root's height; leave it out, or ground: true, to SET IT DOWN: the bottom of its root prim on the terrain under x,y, plus lift metres; by: "all" rests the whole linkset's lowest point instead); rx,ry,rz (degrees; none = the rotation it was kept with); count (default 1, max 32); spacing (metres along +X between copies, default 2); atRoot (default true: the root prim lands at x,y,z; false: the object's centre does); param (on_rez's start parameter, default 0) builder role. Rezzes a COPY of an object item from the box's root prim contents, through the scene's own task-inventory rez (the path llRezObject takes, Scene.RezObject) but with no 10 m reach and no script in the box. The item stays in the box; a no-copy item is refused with 409, because the rez would take it out. The copy belongs to the box's owner, in the box's group, is named after the item and carries its description; its scripts start where it lands (on_rez(param), and a fresh script's state_entry), and the box is its rezzer. Returns item, itemId, requested, count, itemKept, and rezzed: per object uuid, name, pos, prims, scripts. 400 for an item that is not an object, 404 for a missing box or item.
put_notecard uuid, name, text (at most 64 KB of UTF-8; 413 above), description (default "written by Gaia") builder role. Writes a notecard into the root prim's contents, in the format osMakeNotecard writes (Linden text version 2, no embedded items, Text length in UTF-8 bytes), so llGetNotecardLine and viewers read it. Full permissions for the owner; copy, modify, move and transfer for the object's group, as bundle gives. A notecard of the same name is replaced (replaced counts them); an item of another type with that name stays, and OpenSim then names the notecard <name> 1 (the answer's name is what it got). Returns name, item, asset, bytes. With the default MaxBodyBytes (65536) a body that big is refused by the body limit first, also with 413.
despawn uuid also drops any stored wander config.
move_object uuid, x,y,z x, y clamped inside the region (0..255.99 on a 256 region).
rotate_object uuid, rx,ry,rz (Euler degrees)
scale_object uuid, x,y,z or factor a group resize. Resizing a SOLID prim leaks physics memory; see physics-and-walkability.md.
spawn_mesh file (a *.meshdata.json path), x,y,z (required); scale (multiplier) or size (target largest dimension, metres); name; texture (uuid); normal (uuid of a normal map); mapping:"planar"; lods [medium, low, lowest file paths]; physics "box" (default) / "mesh" / a meshdata file path encodes an SL mesh asset server-side and rezzes it. Returns uuid, meshAsset, scale, animesh. See meshes.md for the file format.
spawn_mesh_instance asset (a meshAsset from spawn_mesh), x,y,z, sx,sy,sz, yaw (degrees), texture, name another object wearing an existing mesh asset (the viewer fetches the asset once).
verify_mesh asset decodes a stored mesh the way OpenSim does. OpenSim's decoder is lenient; passing this proves nothing about the viewer.
upload_texture file (a raw J2K codestream, must start FF 4F), name returns texture. Encode with MCT=0 (textures.md).
upload_sound file (must start OggS), name returns sound.
set_script uuid, script (LSL text), name (default wb-script) adds the script to the ROOT prim and starts it under the default engine (YEngine here), bypassing the rez-script permission gate. A script of the same name is replaced; a different name is added alongside. started=true is NOT the compile verdict (scripts.md).
put_inventory uuid, asset, name puts an existing asset (animation, sound, texture, notecard) into the root prim's contents; an item of the same name and type is replaced. Needed because LSL plays animations only by inventory name.
terrain cx,cy,radius (radius >= 0.5), amount, op raise/lower/set/flatten cosine falloff inside the radius; heights clamped to 0..4000. op=set with radius=0.5 at an integer centre sets exactly one vertex.
set_sit_target uuid, x,y,z (root-local metres), rx,ry,rz (degrees) applies at the sitter's NEXT sit.
bundle box (uuid), objects [uuids], name, optional file keeps a copy of the objects as ONE linkset in the box's contents (and optionally as XML on disk); the scene is not touched.

The region (gaia)

command body notes
set_time fraction 0..1, freeze (default true), daylength (s), dayoffset freeze pins the sun with a ~1000-day day length; freeze=false with a daylength runs a day cycle. 0.25 morning, 0.5 midday, 0.75 dusk.
sky {} read; enabled bool; storm 0..1 to force a storm look, -1 to return to the clock the module's once-a-minute storm blend of the region environment's sky and water (weather-and-light.md). Baselines and waterTune live in worldbridge-sky.json beside OpenSim.ini.
save_world none forces a scene backup to the database. Call before stopping OpenSim.
console command runs a region console command. Off unless [WorldBridge] AllowConsole = true (403 otherwise): it can run anything, shutdown and create user included. Output goes to OpenSim.log, not the response. More reliable than the REST console. Do not send a command that prompts (create user with fewer than six arguments does).
create_account first, last (default Resident), password, email (default <first>@paracosm.local) account, password, inventory and home (the middle of the default region), as the console's create user does. An existing account is reported with existed: true and left alone.
grant_estate_manager avatar makes an avatar an estate manager.

Avatars, NPCs and chat (gaia)

command body notes
teleport avatar (uuid or name; default the one visitor present, 400 if several), x,y,z, optional lx,ly,lz look direction x, y clamped to the region (default its middle). With a look direction the arrival faces it.
sit avatar (uuid or name; default the one visitor present, 400 if several), uuid (object or part) seats the avatar server-side (the NPC sit path, HandleAgentRequestSit): the object's sit target places them, wherever they are in the region.
stand avatar stands a seated avatar up.
chat_send message, from (default "Gaia"), channel, shout, region: true or region_wide: true (region-wide, as llRegionSay; a STRING region names the region instead), avatar (speak near them), scripts (default true), from_uuid (default the estate owner) avatars see it on channel 0, AND scripts' llListen hear it (delivered through the world-comm module, as an object's llSay is); speaking as the estate owner, an owner-checking listener accepts it. wb.region_say(ch, msg) is chat_send with region=True; wb.region_say(ch, msg, region="Isle2") sends region="Isle2", region_wide=True.
npc_create first, last, x,y,z default appearance. An NPC counts as an agent for sensors.
npc_move npc, x,y,z, run move-to-target. It can fly, so it proves nothing about stairs (physics-and-walkability.md).
npc_say npc, message, channel
npc_remove npc
give_money amount, avatar a display-only balance push (no economy).
favor action award/get, avatar, amount, reason the persistent ledger shown as the viewer balance (sharing-and-inventory.md).

Objects to people, groups (gaia)

command body notes
give_object uuid, avatar transfers ownership with full permissions.
give_inventory asset, avatar, name puts a stored asset into the avatar's inventory, full permissions.
wear uuid (object), avatar, point (attachment point, default 0), append (default true) attaches the object to the avatar server-side, with no permission dialog. An object the avatar does not own is first made theirs, with full permissions (as give_object). Its scripts get attach().
unwear avatar, and one of uuid, name (substring) or all detaches matching attachments into the avatar's inventory; returns their names.
give_wearable avatar, kind skin (default) / eyes / hair / shape / alpha, name, params {id: value}, textures {index: uuid} makes a body part (an alpha is clothing) as an LLWearable asset and puts it in the avatar's inventory; they wear it from there and their viewer bakes it.
group action ensure (name, charter) / add (group, member, role owners or everyone) / share (group, optional prefix) / members (group) needs Groups enabled (sharing-and-inventory.md).

Rigged creatures (gaia)

command body notes
store_anim file (.anim, under 2 MB), name returns anim.
play_anim uuid, anim, name object animation on an animesh (same as llStartObjectAnimation).
stop_anim uuid, anim (omit to clear all)
wander uuid, speed, steps (default 14, 3..64), radius, cx,cy, faceoffset (deg), leader, trail a terrain-following closed keyframed loop. Persisted in worldbridge-wander.json.
behavior uuid, walk_anim, pause_anim+pause_kind or pauses[{anim,kind}], walk_secs, pause_secs; or off alternates walking and pausing with jitter. Persisted in worldbridge-behavior.json.
roam uuid, walk_anim, pauses, speed, cx,cy, radius, leader, and more; {status: true} reads; {uuid, off: true} stops a nomadic wander over the real ground and round obstacles, which supersedes wander + behavior for creatures. Persisted in worldbridge-roam.json. The full body is in motion.md. status gives per roamer uuid, name, source (op, or notecard for one that registered itself), walking, activity, secondsLeft, at, dest, range {cx,cy,r}, walkAnim, pauses [{anim,kind}], trip, drinkIn. off on an object holding a "roam" notecard saves it as {"off": true} (keptOff: true), so a restart does not set it roaming again from its notecard.

Behaviour worth knowing

The MCP server

mcp/worldbridge_mcp.py speaks MCP over stdio (newline-delimited JSON-RPC 2.0), uses only the Python standard library, and is configured by WORLDBRIDGE_URL and WORLDBRIDGE_TOKEN or WORLDBRIDGE_TOKEN_FILE (it reads the token once, at start), and optionally WORLDBRIDGE_REGION. Every world_* tool (world_call too) takes an optional region argument (name or uuid), sent as ?region=, so it never collides with a body field; without one the call goes to WORLDBRIDGE_REGION, else the simulator's default region. It exposes 26 tools:

group tools
reading (7) world_state, world_list, world_heightmap, world_avatars, world_chat, world_raycast, world_contents
building and the region (12) world_spawn, world_spawn_mesh, world_upload_texture, world_set_script, world_move, world_recolor, world_despawn, world_terrain, world_teleport, world_save, world_rez_item, world_put_notecard
creatures (1) world_roam
anything else (1) world_call {command, body}: any WorldBridge command by name
the AI's own viewer (5) viewer_look (place the camera, return the picture as an image), viewer_camera_reset, viewer_walk, viewer_say, viewer_call (any LEAP operation); these need the viewer and its bridge (viewer.md)

Whichever token you give it determines what it can do; a builder token gets 403 on world_terrain. Setup: quickstart.md section 10.

Changing the module

build/build_server.py builds WorldBridge as part of the server. To rebuild only the module against an existing server: dotnet build worldbridge/WorldBridge.csproj -c Release -p:OpenSimBin=<bin>/ (net8.0; OpenSimBin defaults to ../opensim-src/bin/, the original checkout's layout), which writes worldbridge/bin/Release/WorldBridge.dll. Swap it into bin/ during a restart. See operations.md for the region-module attributes Mono.Addins requires and for a swap that does not race a supervisor. A quick way to confirm a new DLL is live: call a command only the new build knows; the old build answers 404, the new one 400 (missing fields).