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
- Binds
127.0.0.1only. A non-loopbackBindAddressis logged as a warning and forced back to loopback. - Default port 9002. Config lives in
[WorldBridge]inOpenSim.ini:Enabled,BindAddress,Port,MaxBodyBytes(default 65536),TokenFile,AllowConsole,FileRoots, and optionallyAuthTokenand the role tokensTokenObserver,TokenBuilder,TokenGaia(written bymake_config.pyfrom the.envkeysWORLDBRIDGE_OBSERVER_TOKEN,_BUILDER_TOKEN,_GAIA_TOKENwhen set). config.md has every key and its default. - Every request is
POST /<command>with a JSON body andAuthorization: Bearer <token>. - There is no built-in token. With no valid
AuthToken, WorldBridge reads the gaia token fromTokenFile(WorldBridge.tokenbeside OpenSim), and on the first start generates a random one there. A token shorter than 16 characters, or the retired demo valueparacosm-worldbridge-token, is refused.tools/wb.pyand the MCP server read the file throughWORLDBRIDGE_TOKEN_FILE. - Status codes: 405 for a non-POST, 401 for a missing or unknown token, 403 when the token's role is too low
(the body names the role needed and the role held), 403 for a file outside
FileRootsor forconsolewhileAllowConsoleis false, 413 for a body overMaxBodyBytes, 404 for an unknown command, 400 for invalid JSON or missing required fields, 409 whenrez_itemnames a no-copy item, 429 when a token is over its rate limit (seeworldbridge/HARDENING.md), 404unknown region(listing the regions) for a region the simulator does not run, 503 withRetry-Afterwhen the request queue is full or the region is shutting down (below; nothing ran, so it is safe to send again), 500 with adetailwhen the scene throws. - "The" avatar. An op that acts on an avatar (
teleport,sit,stand,wear,unwear,worn,give_*,grant_estate_manager, localchat_send) uses the one visitor present when none is named. With several visitors and noavatar, or a name fragment matching several, it answers 400 "several visitors present: name the avatar" (listing them) instead of picking one. A uuid or an exact full name always resolves. HttpListeneranswers 411 to a POST with noContent-Length: send a body (even{}) on every call, includingstate. (tools/wb.pyalways does.)- Roles.
observermay callstate,list,heightmap,avatars,chat,inspect,worn,contents,roam_notecard.builderaddsspawn,recolor,rez_itemandput_notecard. Every other command needsgaia. The token file's token (orAuthToken) is always a gaia token. Give an assistant the lowest role it needs. - Every command is appended to
WorldBridge.audit.logbeside the binaries (UTC time, remote endpoint, role,tok:+ the first 6 hex of SHA-256 of the token used, command, region, body truncated at 4000 characters,passwordvalues masked). It rotates at 16 MB, keeping one previous file. - Large inputs are file paths, not bodies. Meshes, textures, sounds and animations are passed as a path the
OpenSim process reads from its own disk, and the path must lie inside one of the
FileRootsfolders (defaultworldbridge-filesbeside OpenSim). Only LSL text travels in the body, so a script over the body limit gets a 413. - Positions are clamped to the region's real size, so the same calls work on a 512 or 768 var region
(
worldbridge/VAR-REGIONS.md). The clamp keeps a position strictly inside (up to size - 0.01 m): a position ON the border would cross into a neighbouring region when there is one.
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.
- Naming the region.
?region=<name or uuid>in the URL, or"region": "<name or uuid>"(a string) in the JSON body; the URL wins when both are given. Names match case-insensitively. With neither, the request goes to the DEFAULT region: the first one loaded (the first section ofRegions.ini). A region the simulator does not run gets404 {"error": "unknown region", "region": ..., "regions": [names]}. A boolean"region"is not a name: it ischat_send's region-wide flag, and keeps that meaning. - Every command acts in its region: objects, avatars, terrain, the sky, chat heard, NPCs, the favor ledger. An
object in another region is
404 no such object. Var regions are per region: a 512 region beside a 256 one clamps and defaults to its own size. stateanswers for its region, and lists every region:regions[{name,uuid,sizeX,sizeY,location{x,y} (grid cells),isDefault,stateFiles}].- State files are per region. The default region keeps today's names (
worldbridge-roam.json, ...); every other region has its own, the region's name inserted before.json(worldbridge-roam.GiftTest2.json, with any character other than a letter, digit,-or_made_). So a house that adds a region keeps all it had, as long as the new section goes AFTER the existing one; put a new region first and it takes over the unsuffixed files. Which region is the default is fixed for the life of the process (a region restarted alone keeps its files). - The audit log's region column names the region each command ran in.
wb.pytakes a bodyregion=...per call, orWORLDBRIDGE_REGIONfor every call that names none; the MCP tools take aregionargument (below).- Not yet: lookups ACROSS regions (an object or avatar is found only in the region named), region-global
coordinates, and moves or roams that cross a border on purpose (
worldbridge/TODO.md).
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) |
- The reads have their own lane so a poller stays quick while a builder is busy: measured on a scratch server,
stateanswered in a median 39 ms (max 165 ms) while 68spawn_meshcalls queued for up to 10 s in the work lane. - A lane that is full answers at once:
503 {"error": "busy", "command", "lane", "queued", "queueLimit", "workers", "retryAfterSec"}with aRetry-Afterheader (whole seconds, at least 1).retryAfterSecestimates how long the lane ahead takes to drain. Nothing ran, and the rate-limit charge is given back.tools/wb.pywaits and resends, as it does for a 429 (up to 10 tries, the wait spread by a random factor so a burst does not return together). - A request that waited longer than
QueueWaitSec(20 s) is answered503 "waited too long"instead of run: its caller has probably given up, and a spawn run then would be one nobody saw succeed. - Commands in the work lane run up to
MaxConcurrentat once, by default ONE: world-changing commands still run one at a time, as they always did, while reads get their own lane and a burst queues instead of piling up. The scene tolerates more (the viewers' own threads act on it concurrently) and WorldBridge's own state is locked per piece, but only spawn, spawn_mesh and list were load-tested side by side, so raise it after testing your workload. - The audit line is written when a command RUNS, with its region; a request refused with 401, 403, 404 unknown region, 413, 429 or 503 is not audited.
- Shutdown: a region that starts closing (the start of
Scene.Close, before its last backup) stops taking requests (one still queued for it gets503 "region shutting down") and waits up to 10 s for those running in it; the other regions go on being served. When the last region goes, the channel closes: queued requests get503 "shutting down", running ones finish (up to 15 s), then the listener stops. Aconsole shutdownsent through WorldBridge runs the shutdown on its own worker thread and is the one request not waited for (it gets no answer: the process exits). statereports the queue:queue{workers,queueLimit,running,queued,readWorkers,readQueueLimit,readRunning,readQueued,queueWaitSec,served,rejected}.
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
-
Everything WorldBridge creates is owned by the estate owner. That is why scripts it adds pass owner checks, and why
region_say(chat_sendwithregion=True, spoken as the estate owner) can talk to owner-filtered listeners. -
set_script,put_inventory,put_notecard,bundle,contentsandrez_itemact on the root prim. A multi-prim object's children are not reached. -
A box of gifts is a box of copies.
bundlekeeps copies of objects in a box,contentslists them,rez_itemtakes a copy back out anywhere in the region (the item stays, so one box serves again and again), andput_notecardleaves a note beside them. -
A creature that carries its own roam. Roam knows a creature by its uuid, and a copy out of a box has a new one. So an object that enters the scene holding a notecard named exactly
roamin its root prim registers itself, as theroamop would (worldbridge/RoamNotecard.cs): on a rez from a box or from inventory,rez_item, a duplicate, an OAR load (OnObjectAddedToScene), and once at startup after the saved configs are re-armed (OnPrimsLoaded; during the load itself the event is ignored). The notecard is theroambody as JSON withoutuuid. Any animation value (walk_anim,flee_anim,drink_anim,pause_anim,pauses[].anim,down_anim/up_anim: any keyanimor ending_anim) may be an asset uuid or the NAME of an animation in the root prim;cx,cydefault to where the object stands andradiusto 16. Nowater: no drink trips.wary_ofandhuntsname kinds, so a world without them never triggers them; aleaderthat is not here leaves it to go its own way. The resolved body is saved by uuid (with"source": "notecard"), so a restart re-arms that and never reads the notecard again; an object already registered keeps its config. A notecard that is not JSON, or names an animation the object does not hold, is logged as a warning and the object is not registered. Example:{"walk_anim": "walk", "pauses": [{"anim": "graze", "kind": "graze"}, {"anim": "idle", "kind": "idle"}], "speed": 0.75, "clear": 1.9, "max_slope": 0.6, "height": 3.1, "body_len": 3.0, "body_wid": 2.2, "leg_min": 10, "leg_max": 28, "pause_min": 25, "pause_max": 70, "kind": "elephant", "radius": 24}roam_notecard {uuid}writes that text from a live, tuned roamer. -
The wander and behaviour configs are re-armed on
OnPrimsLoaded, not inRegionLoaded. Re-arming inRegionLoadedran before the stored objects were in the scene, so every lookup missed and every config was pruned: the whole herd silently stopped on every restart. -
The module keeps its own state in small JSON files in the server's
bin/(the share group, the favor ledger, the sky baselines, the wander, behaviour and roam configs:worldbridge-share.json,worldbridge-favor.json,worldbridge-sky.json,worldbridge-wander.json,worldbridge-behavior.json,worldbridge-roam.json), one set per region: these names are the default region's, and any other region's carry its name (worldbridge-roam.<Region>.json, see "Several regions"). The first three are written to OpenSim's working directory, the rest beside the binaries; both arebin/when OpenSim is started from there, astools/run_server.pydoes.tools/backup.pycopies them all (everyworldbridge-*.json). -
set_script's superseded-asset check looks at every region of the simulator (they share one asset store): a task item in any region, and everyone in any region's favor ledger, keeps an old script asset.
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).