← Paracosm·Docs/Reference
Contents · Var regions

WorldBridge on OpenSimulator var regions

WorldBridge now works on OpenSimulator var regions (the modern "mega regions": one Scene that is 512 x 512 = 2x2, 768 x 768 = 3x3, and so on), not only the classic 256 x 256. Every region-coordinate default and clamp reads the bound Scene's real size (m_scene.RegionInfo.RegionSizeX / RegionSizeY). Heightmap indexing uses the heightmap's own Width / Height. A 256 region behaves exactly as before. The one visible difference there is two new response fields: state gains sizeX/sizeY, and heightmap gains samplesX/samplesY.

Facts about var regions in this OpenSim (from OpenSim/Framework/RegionInfo.cs, DoRegionSizeSanityChecks): each side is a multiple of 256, up to Constants.MaximumRegionSize = 4096, and the region is forced square because of viewer limits. So RegionSizeX == RegionSizeY in practice. The code still reads both, in case a future OpenSim lifts that rule.

Helpers added

WorldBridgeModule.cs:1001-1004: RegionSizeX, RegionSizeY (float metres, falling back to Constants.RegionSize if no scene is bound yet), RegionMidX, RegionMidY (half of each; 128 on a 256 region). Roam.cs shares them through the partial class.

Every site changed

Line numbers are on branch ship/var-regions, with the old line on 2c9f690 in brackets.

# Where Before After
1 WorldBridgeModule.cs:715-716 state (old 713-714) region, regionUUID, avatars, objects adds sizeX, sizeY (metres, from RegionInfo), so a client can learn the extent
2 WorldBridgeModule.cs:1045-1070 heightmap (old 1034-1050) fixed n = 64, stepX = w/64, stepY = h/64; body ignored optional {"grid":N} (default 64, clamped [8,512]); one square step ceil(max(w,h)/N); adds samplesX, samplesY. Contract below
3 WorldBridgeModule.cs:1560 wander (old 1540) cx, cy default 128 default RegionMidX/Y. The clamp stays [0,10000], which already covers any var region
4 WorldBridgeModule.cs:1808-1813 create_account home (old 1788) SetHome(..., new Vector3(128,128,26)) the middle of the home region h, by h.RegionSizeX/Y (a GridRegion, which may be the grid's default region and not this one). Falls back to 128 if the size is 0
5 WorldBridgeModule.cs:2302 chat_send (old 2277) speaks from (128,128,30) when no avatar is named (RegionMidX, RegionMidY, 30)
6 WorldBridgeModule.cs:2329 teleport (old 2304) x,y default 128, clamp [0,256] default RegionMid, clamp [0,RegionSize]
7 WorldBridgeModule.cs:2406 move_object (old 2381) clamp [0,256] clamp [0,RegionSize]
8 WorldBridgeModule.cs:2458 npc_create (old 2433) default 128, clamp [0,256] default RegionMid, clamp [0,RegionSize]
9 WorldBridgeModule.cs:2473 npc_move (old 2448) default 128, clamp [0,256] default RegionMid, clamp [0,RegionSize]
10 WorldBridgeModule.cs:3039 raycast (old 3014) origin default (128,128,30) (RegionMidX, RegionMidY, 30). The clamp stays +/-1e4
11 Roam.cs:210-211 roam range centre (old 210) cx, cy default 128, clamp [0,256] default RegionMid, clamp [0,RegionSize]
12 Roam.cs:212-214 roam radius (old 211) clamp [5,180] clamp [5, 180 * max(SizeX,SizeY) / 256]: 180 on 256, 360 on 512, 540 on 768. 180 m is a 256 region's half-diagonal (the whole region from its middle), so the same reach scales with the region
13 Roam.cs:704-724 BuildGrid (old 703-709) grid window = range +/- 2 m, clipped to the heightmap same, plus a hard cap MaxWalkGridSide = 1024 cells a side, centred on the range. It never binds on a region of 1024 m or less, because the heightmap bound is tighter

Later (2026-09-27, multi-region, ship/p2-wb): the position clamps in rows 6-9 and rez_item stop at RegionMaxX/Y = size - 0.01 m instead of the size itself. OpenSim's region is [0, size): a position clamped to 256.0 sat on the border, and beside a neighbouring region an object moved there crossed into it (seen on a two-region test server). The roam range centre (row 11) still clamps to the size; it is a centre, not a position.

Build: dotnet build worldbridge/WorldBridge.csproj -c Release -p:OpenSimBin=C:/CLD/paracosm/opensim-src/bin/ gives 0 warnings, 0 errors. The -p:OpenSimBin= override is needed only in a worktree, which has no opensim-src/. It points the HintPaths at the live bin, read-only. The csproj is unchanged.

The heightmap op contract

Request body is optional: {} or { "grid": N }.

region {} {"grid":256} {"grid":512}
256 64 x 64 @ 4 m (identical to before) 256 x 256 @ 1 m 256 x 256 @ 1 m
512 64 x 64 @ 8 m 256 x 256 @ 2 m 512 x 512 @ 1 m
768 64 x 64 @ 12 m 256 x 256 @ 3 m 384 x 384 @ 2 m

Why this option: the default keeps the output shape stable (64 x 64) on every size, so existing renderers still get a grid they can draw. grid lets a client ask for enough resolution on a big region, where 12 m point samples alias small features like a jetty or a narrow sandbar. The 512 cap bounds a response to 262k numbers (about 2 MB of JSON). One square step was chosen over independent per-axis steps so that cells stay square even if a non-square region ever becomes legal. On today's forced-square regions the two choices are identical.

Walk-grid cost on a 768 region (roam)

The walk grid is N x N one-metre cells. N is the range window, capped by the heightmap to at most side - 3: 253 on 256, 509 on 512, 765 on 768. The things that fill it are:

Measured on the live 256 region (OpenSim.log, 2026-09-27): a 157 x 157 grid builds in 0.3-0.7 s. That is about 30 us per cell for one downward physics ray and five bilinear ground reads. The worst case on a 768 region, N = 765 (585k cells), comes to:

These costs are sane for 768. Past 1024 on a side, which only happens on a var region bigger than 4x4 (OpenSim allows up to 4096), a 4093^2 grid would be about 150 MB held and 200 MB per plan. MaxWalkGridSide = 1024 caps N, so the worst case anywhere is 1M cells: about 9.4 MB held, 12.6 MB per plan, and about 30 s to build. Cells outside a cropped grid are simply not walkable, because Usable's g.In() check rejects them. A water trip longer than about 1 km on such a region will fail to plan and retry a minute later. It will not hang or throw.

Region-size-dependent things left alone, and why

Client-side 256 assumptions found in passing were not changed, because the scope here is the module:

How to test on a 512 x 512 region

Region config (bin/Regions/Regions.ini, one section per region):

[Ocean512]
RegionUUID = <new uuid>
Location = 1000,1000          ; grid units of 256 m: this region covers 1000..1001 x 1000..1001
SizeX = 512                   ; multiple of 256; must equal SizeY (OpenSim forces square)
SizeY = 512
SizeZ = 4096
InternalAddress = 0.0.0.0
InternalPort = 9000
AllowAlternatePorts = False
ExternalHostName = SYSTEMIP

Neighbours of a var region must sit at least SizeX/256 grid units away. For a 3x3 the keys are SizeX = 768 and SizeY = 768. Do not confuse var regions with the old CombineContiguousRegions "megaregions", which are a different, retired mechanism. The console prints Region Ocean512 size set to <512,512> on start. Use a var-region-aware viewer.

Checks, in order (each one is a WorldBridge POST):

  1. state returns sizeX: 512, sizeY: 512.
  2. heightmap {} returns width 512, stepX 8, samplesX 64, samplesY 64. {"grid":256} gives step 2 and 256 samples. {"grid":512} gives step 1 and 512 samples. Compare a few samples to terrain save output.
  3. teleport {"x":480,"y":480,"z":40} should land at (480,480), where it was clamped to 256 before. With no x/y it should land at (256,256).
  4. move_object {"uuid":..., "x":500, "y":20}: the object should be at x 500. "x":600 clamps to 512 (the edge), so prefer 511 when testing.
  5. npc_create {} should spawn at (256,256,30). Then npc_move {"npc":..., "x":450, "y":450} should walk there.
  6. chat_send {"message":"hi"} with no avatar is spoken from (256,256,30). Stand within 20 m of the middle to hear it in local chat.
  7. raycast {"land":true, "dx":0, "dz":-1, "length":60} should hit land under (256,256).
  8. wander {"uuid":...} with no cx/cy should loop around (256,256).
  9. roam with cx:400, cy:400, radius:100: the log should show roam walk grid (400,400) r100: 204x204 cells ... and the creature should walk there. Also try radius:300 (clamped to 180 before, accepted now), and a water pool across the region. Check that the logged trip grid is <= 509 x 509 and note the build time. It should be seconds, and the region's frame rate should hold while it builds.
  10. create_account for a throwaway user: the GridUser HomePosition should be the default region's middle, which is (256,256,26) if that is the 512 region.
  11. terrain {"cx":450,"cy":450,"radius":10,"amount":2} should report changed > 0. This op was already var-safe and is here as a regression check.

Run the 256-region regression too: heightmap {} is still 64 x 64 at step 4, teleport {} still goes to (128,128,30), and the live roamers' grid log lines keep the same sizes as before, for example (105,115) r76: 157x157.