Operations: building, running and restarting the region
Building OpenSimulator
1. It is .NET 8, not Mono
OpenSimulator 0.9.3.0 builds and runs on .NET 8 (SDK plus the .NET 8 and ASP.NET Core 8 runtimes). Mono is not
needed. Build with runprebuild.bat, then compile.bat (which is dotnet build -c Release OpenSim.sln).
- The whole server (zip hash, patches, Prebuild, compile, sqlite3.dll, WorldBridge, config) is one command:
python build/build_server.py <out>. Seebuild/README.md. Steps 2 and 3 below are what it automates. - Learned: an early plan assumed Mono; the build recon found .NET 8.
- Note: "OpenSim" is an ambiguous name. The virtual-world server is OpenSimulator (opensimulator.org), not
Stanford's
opensim-corebiomechanics simulator.
2. Run runprebuild.bat from cmd, not from a POSIX shell
Its /excludedir = "obj | bin" argument is mangled when passed through bash.
3. Put the official native SQLite in bin/
The shipped bin/lib64/sqlite3.dll is a Mono dllmap build that links the absent MSVCR90.dll. .NET 8 ignores the
dllmap (bin/Mono.Data.Sqlite.dll.config), and DllImport("sqlite3") finds bin/sqlite3.dll first, so the fix is
to ADD the official self-contained x64 sqlite3.dll (SQLite 3.47.0 here) as bin/sqlite3.dll; bin/lib64/ stays
as shipped. (An earlier note said it replaces the lib64 copy; on the reference server lib64 was never changed.)
4. Building an OpenSim project writes into the live bin/
While the region runs, the copy of a loaded DLL into bin/ fails on the file lock (the output waits in
obj/Release/), and up-to-date dependencies are not recopied. Swap the DLL during a restart. After building core
OpenSim projects, dotnet build-server shutdown releases the build nodes.
- See:
opensim-patches/*/README.md(each patch names the DLL it produces).
Running headless
5. Use the REST console, not the basic console, with no terminal
The default local console throws with no real console, and -console=basic with a dead stdin busy-loops on EOF
and floods the log. Launch from bin/ with dotnet OpenSim.dll -inifile OpenSim.ini -console=rest, with
ConsoleUser, ConsolePass and console_port set under [Network] (not [Startup]).
tools/run_server.py startdoes exactly this (superviserestarts it whenever it exits); the console keys come from.env(config.md). With an emptyConsoleUserthe REST console refuses every session.- In practice the WorldBridge
consolecommand is the reliable way to run console commands; its output goes toOpenSim.log.
6. Enable OSSL by creating config-include/osslEnable.ini
OpenSim.ini already includes osslDefaultEnable.ini (AllowOSFunctions true), which chains to
config-include/osslEnable.ini. Create that file to raise the threat level or allow-list functions, then restart.
In this build osSetTerrainTexture (a High-threat function) was allowed to ESTATE_OWNER; scripts added by
WorldBridge are owned by the estate owner, so they qualify.
7. Never run two OpenSim processes against one SQLite store
If you run OpenSim under a process supervisor that relaunches it, never also start it by hand.
Logs
All of them are in bin/ (OpenSim's working directory):
| file | written by | size limit |
|---|---|---|
OpenSim.log |
OpenSim, through log4net | rolls at 20 MB; five old files kept, OpenSim.log.1 (newest) to OpenSim.log.5; about 120 MB at most |
OpenSimStats.log |
OpenSim, only while stats record start is on |
rolls the same way |
WorldBridge.audit.log |
WorldBridge: one line per request (time, caller, role, tok: id, command, body with passwords masked) |
moved to WorldBridge.audit.log.1 when it passes 16 MB (one old file kept); about 32 MB at most |
opensim-console.out.log |
tools/run_server.py start: OpenSim's stdout and stderr |
moved to .1 at a start when it is over 20 MB; it still grows while that one process runs |
- Where the log config lives. OpenSim 0.9.3.0 on .NET 8 configures log4net from
OpenSim.exe.configin its working directory (Application.cs:XmlConfigurator.Configure(new FileInfo("OpenSim.exe.config"))), or from-logconfig <file>if given; the first log line says which ("configured log4net using default OpenSim.exe.config"). There is noOpenSim.dll.config. Upstream's file uses a plainFileAppender, so itsOpenSim.loggrows forever (13.5 MB after four days on the reference server). - Ours.
config/OpenSim.exe.configis upstream's file with the two file appenders changed toRollingFileAppender(rollingStyle Size,maximumFileSize 20MB,maxSizeRollBackups 5,staticLogFileName true, so the live file is alwaysOpenSim.logand the tools that read it need no change).tools/make_config.pyputs it inbin/(config.md); a restart makes it take effect. The appender names stayLogFileAppenderandStatsLogFileAppender: OpenSim finds them by name (ServerBase.RegisterCommonAppenders), and[Startup] logfilestill renames the file. - The console copy. With
-console=restOpenSim prints every log line to stdout too (RemoteConsole.Output), so underrun_server.py startopensim-console.out.logis a second copy of the log. Underrun_server.py supervise(the systemd unit) stdout goes to the journal instead, which has its own limits. - Following the log on Windows. A roll renames
OpenSim.log. If another program holds it open without delete-sharing at that moment, the rename fails, log4net warns ("Cannot RollFile") and startsOpenSim.logafresh, and the lines since the previous roll are lost. Measured with log4net 2.0.15 on .NET 8 (2026-09-27): Git Bashtail -f OpenSim.logdid not block a roll; PowerShellGet-Content -Waitand a Pythonopen()kept open did. Follow it withtail -f. The repo's tools open the log for a few milliseconds per read. On Linux a rename never fails this way. - Tested offline with OpenSim's own
log4net.dllandconfig/OpenSim.exe.config: 130 MB written through the root logger leftOpenSim.logand exactly five 20 MB backups, newest first; the named appenders areRollingFileAppenders and pass OpenSim'sFileAppendercast. Not yet watched on a running region.
Region-module mechanics
8. A standalone module assembly needs both Mono.Addins attributes
[assembly: Addin("WorldBridge", "0.1")] AND [assembly: AddinDependency("OpenSim.Region.Framework", "0.9.3.0")], with the version string matching OpenSim's VersionInfo exactly, or the plugin scanner ignores the
DLL. The class carries [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "...")].
- Keep the assembly attributes in one file: a second
Properties/AssemblyInfo.csproduced a duplicate-Addin compile error. - Reference the OpenSim DLLs with
Private=falseand setCopyLocalLockFileAssemblies=falseso the build output is only your DLL. - See:
worldbridge/WorldBridgeModule.cs,worldbridge/WorldBridge.csproj.
9. Re-arm persisted behaviour on OnPrimsLoaded
RegionLoaded fires before the stored objects are in the scene. Look-ups there miss.
- Learned: WorldBridge re-armed creature motion in
RegionLoaded, found no objects, pruned every stored config, and every creature froze on every restart.
10. Module API notes (so they need not be re-derived)
Colour: part.SetFaceColorAlpha(-1, rgb, alpha) (face -1 = all). Terrain: scene.Heightmap[x, y] = h, then
ITerrainModule.TaintTerrain(). Shapes: PrimitiveBaseShape.CreateBox/CreateCylinder/CreateSphere. Delete:
scene.DeleteSceneObject(sog, false). Look-up: scene.GetSceneObjectGroup(uuid). Animesh flag:
shape.AnimeshEnabled; object animation: SceneObjectPart.AddAnimation(animId, name). At the C# level
presence.Animator.AddAnimation(uuid, ...) takes an asset id, unlike LSL.
Restarts and deploys
11. Restart only with nobody on the region
Wait for avatars (or state) to report zero. An idle, away visitor still counts. Save first (save_world or
console backup).
12. A full save can outlast a 30 s client timeout
A save after touching every object rewrites the whole region. Pass a longer timeout, or poll state until it
answers, then read the database.
13. Swap a module DLL without racing a supervisor
Windows lets you RENAME a loaded, memory-mapped assembly. Rename the live WorldBridge.dll aside, copy the new one
in, THEN run console shutdown; whatever relaunches OpenSim loads the new DLL.
- Learned: copying after the shutdown raced a supervisor relaunch that came within 1-3 s.
- See:
tools/region_restart.py(console shutdown, wait for exit, copy each staged DLL over its twin with a timestamped.bak, wait for WorldBridge to answer). Under a supervisor it lets the supervisor relaunch OpenSim; when no new process has appeared 90 s after the copy, it starts OpenSim itself (tools/run_server.py). It needs WorldBridge'sconsoleop (AllowConsole = true); itsbin/isPARACOSM_BIN(tools/paths.py). See CONFLICTS.md on the copy window.
13a. WorldBridge answers before the region has finished starting
The last startup step creates every object's script instances. A spawn or delete racing that pass crashed the
region (a NullReferenceException in SceneObjectGroup.CreateScriptInstances, fatal), twice on 2026-09-25.
tools/region_restart.py now returns only after the log says the script instances are initialised, and the core
is patched to skip a deleted group (opensim-patches/startup-script-guard).
14. A region restart does not re-run state_entry
Scripts resume their saved state. A controller that applies a setting only in state_entry or on change will not
re-apply it after a restart; re-lay the script or have it re-apply on its next tick.
15. A prim restores its last looping sound at startup, before its script runs
Clear it (llStopSound()) before applying a sound mode, or a silenced region plays again after a restart.
- See:
tools/sound_mode.py(loop_setup).
16. A restart clears BulletSim ghost bodies
Objects load from the database with phantom already set, so no stray body is built. See physics-and-walkability.md.
The patches in opensim-patches/
Each patch is a unified diff against pristine 0.9.3.0 (opensim-patches/<name>.diff), applied in the order of
opensim-patches/series by build/build_server.py; its folder holds the notes.
| patch | fixes | DLL |
|---|---|---|
asset-instr |
diagnostics: with PARACOSM_ASSET_INSTR=1, logs every GetAssets request, miss and serve (off by default) |
OpenSim.Capabilities.Handlers.dll, OpenSim.Region.ClientStack.LindenCaps.dll |
sqlite-groups |
Groups V2 stores only to MySQL/PostgreSQL; adds SQLiteGroupsData and the config |
OpenSim.Data.SQLite.dll |
bulletsim-ghost |
a mesh set phantom (or deleted) while its physics asset is still being fetched gets a new, untracked, solid body | OpenSim.Region.PhysicsModule.BulletS.dll |
startup-script-guard |
a group deleted during the startup script pass crashed the region | OpenSim.Region.Framework.dll |
terrain-script-pbr |
osSetTerrainTexture from a script set only the legacy terrain textures; a PBR viewer is sent the PBR slots |
OpenSim.Region.CoreModules.dll |
sqlite-offlineim |
"Offline Message Module V2" stores only to MySQL/PostgreSQL; adds SQLiteOfflineIMData (offline IM on SQLite) |
OpenSim.Data.SQLite.dll |
sqlite-profiles |
four bugs in the SQLite profile storage: the usersettings table never created, preferences never stored, classified delete, app data |
OpenSim.Data.SQLite.dll |
Each README has the deploy steps for a running server. The two SQLite storage patches must be in
OpenSim.Data.SQLite.dll before the config that uses them (config.md): with offline IM set to
Offline Message Module V2 on SQLite and an unpatched DLL, OpenSim does not start. docs/upstream/ has a write-up
of each patch for the OpenSimulator bug tracker.
Memory and CPU
17. Never resize a SOLID prim from a script
Every PRIM_SIZE / llSetScale change on a non-phantom prim makes BulletSim build a new collision shape that is
never freed (about 75 KB each). Moving (llSetPos) does not leak.
- Rule: any script that animates a prim's size calls
llSetStatus(STATUS_PHANTOM, TRUE)instate_entry. Decorative things (smoke, steam, blossoms, reeds, plants, water) should be phantom anyway. - Learned: eight chimney-smoke puffs resizing four times a second leaked 2.4 MB/s; the region reached 8.3 GB, the host swapped, the region froze for 22 s and dropped the visitor.
- See:
tools/phantomize.py(applies phantom live to named families without resetting script state).
18. Find a leak by measuring one suspect at a time
Measure private bytes over 60 s with nothing happening; force gc (no drop means a real leak); yeng suspend /
yeng resume isolates scripts (YEngine's commands; scripts suspend belongs to XEngine and does nothing here);
yeng ls lists every script with its event count; silence one family and re-measure.
- Warning signs: a region that grows while idle,
Timeout detected for thread "Incoming Packets"orN ms since Watchdog last raninOpenSim.log. - See:
tools/probe_memory.py(one suspect per run: scripts, terrain, mesh).
19. Looping keyframed motion costs CPU every tick
OpenSim's keyframe timer moves each looping object about 20 times a second: 266 swaying objects were 16% of a core with nobody on the region. Gate motion on a visitor being near (motion.md).
- Learned: a first reading of 3% was taken before every loop had started; measure cost only once the thing is fully running.
Logging in with a viewer
20. Select the grid through the grid list, never with --loginuri
Firestorm's command line does not parse a single --loginuri <uri> in this build: the value is silently dropped and
the viewer falls back to another grid (once, before the Paracosm patches, to Second Life's login server with the
local account's name and password; viewer.md section 4). By hand: add the grid once via Preferences, OpenSim, Grid
Manager, "Add new grid" with the login URI (the viewer fetches get_grid_info), then select it on the login
screen. From a script: write the grid to the viewer's user_settings/grids.user.xml and pass
--set CurrentGrid <host:port> with --login <first> <last> <password>, as tools/run_viewer.py does. Accounts can
be made with WorldBridge create_account (quickstart.md section 9).
21. Firestorm's UI is custom-drawn
UI Automation sees only the window frame. Drive anything inside it by screenshot and OCR plus clicks. See verification.md for the look-and-close procedure.
The bot
bot/ is a .NET 8 console project using LibreMetaverse 3.1.6 from NuGet. API differences from the old libomv that
cost time:
- The namespace is
LibreMetaverse, notOpenMetaverse. - Login is async:
await client.Network.LoginAsync(loginParams, CancellationToken.None)returnsTask<bool>. Self.Chattakes four arguments(msg, channel, ChatType, splitLargeMessages).- There is no
Self.Teleportand noMovement.AtPos; move withclient.Self.AutoPilotLocal(x, y, z)(andAutoPilotCancel());Self.SimPositionreads the position. - Do not set
<InvariantGlobalization>true</InvariantGlobalization>: LibreMetaverse.Utils needs the en-US culture and throwsCultureNotFoundExceptionat login.
COLLADA from Blender
Blender 4.2 and later replaced OpenCOLLADA with a stub collada_support extension whose
bpy.ops.export_scene.collada exports empty scenes (0 triangles) with no armature or skin options. For a rigged
.dae use the SL "Better Collada" add-on (bpy.ops.export_scene.dae) or the real OpenCOLLADA exporter in Blender
4.1 or earlier (bpy.ops.wm.collada_export). WorldBridge's spawn_mesh does not need COLLADA at all.
A heavy build on the same machine starves the region
A parallel C++ build (msbuild /m) alongside the region stalled OpenSim's watchdog by 5-29 s and held its shutdown
for minutes. Run builds at idle priority (start /low), or pause them around a restart.
Never write a password to a log
WorldBridge once logged console commands and request bodies verbatim, including reset user password and
create_account passwords. Both logs mask password values now. If you script edits to C# regular expressions
from Python, check the bytes afterwards: a "\b" typed into a non-raw Python string becomes a backspace character
and silently breaks the pattern.