| name | opensim-asset-creation |
| description | OpenSimulator object creation workflow - console vs. LSL approaches for all ariadne4j asset types |
| version | 1.0.0 |
| category | gaming |
OpenSimulator Object Creation Workflow
Documents what's possible via OpenSim console vs. what requires in-world LSL/OSSL scripting.
Console Commands That Work
These can be tested directly via MCP/REST console:
| Category | Commands | Notes |
|---|
| Info | show version, show uptime, show users, show regions, show stats | Full support |
| Terrain | terrain load, terrain save, terrain fill, terrain elevate, terrain show | Full support |
| Archives | save oar, load oar, save iar, load iar | Full support |
| Scripts | scripts start, scripts stop, scripts suspend, scripts resume, scripts show | Full support |
| Config | config get, config set, config show | Full support |
| Users | alert, kick user, teleport user, kick user | Full support |
| Objects | show object name, delete object name, backup, edit scale | Modify existing only |
| Logs | get log level, set log level <level> | Debug support |
Console Commands That DON'T Work
These return "Invalid command" or silently fail:
| Command | Result | Workaround |
|---|
create cylinder | "Invalid command" | Use LSL script with llRezObject() |
create box | "Invalid command" | Use LSL script with llRezObject() |
create sphere | "Invalid command" | Use LSL script with llRezObject() |
load xml2 | Silent success, no object added | OpenSim 0.9.3.0 deserializes but AddSceneObject silently drops it |
Root cause: OpenSim console has no object creation commands. The load xml2 command deserializes but silently fails due to UUID collision handling or deserialization errors.
LSL/OSSL Script Approach (Required for Object Creation)
To create new objects, you must:
- Create a prim (rez in-world via viewer or use existing prim)
- Add an LSL/OSSL script to that prim
- The script uses
llRezObject() to create new objects from inventory
Basic llRezObject Pattern
default {
state_entry() {
// Rez a cylinder at the script's location
vector pos = llGetPos();
rotation rot = llGetRot();
llRezObject("MyObject", pos, <0,0,0>, rot, 0);
}
on_rez(integer param) {
// Configure the rezzed object
llSetScale(<0.5, 0.5, 5.0>); // 5m tall
llSetColor(<0.55, 0.27, 0.07>, ALL_SIDES); // brown
}
}
llRezObject Parameters
llRezObject(string inventory, vector position, vector velocity, rotation rot, integer param)
| Param | Description |
|---|
inventory | Name of object in the rezzer's inventory |
position | World position to rez at |
velocity | Initial velocity of rezzed object |
rot | Initial rotation |
param | On_rez event parameter, often used for inventory flags |
Ariadne4j Types and Implementation Status
Console-Testable Types
| Type | How to Test |
|---|
type_sltexture | Console can't create, but existing textures visible via show object |
type_sllandmark | Console can't create, but show regions shows landmark data |
type_slnotecard | Console can't create, but visible in object inventory |
type_slscript | scripts show lists running scripts; scripts stop/start controls them |
type_slsound | Can be configured but not created via console |
LSL Script Required
| Type | Implementation |
|---|
| Prim creation (all types) | llRezObject() with inventory object |
| Particle systems | llParticleSystem() in LSL script |
| Animations | llStartAnimation() / llStopAnimation() |
| Chat triggers | llListen() + llSay() / llShout() / llWhisper() |
| Sound triggers | llPlaySound() / llTriggerSound() |
| IM triggers | llInstantMessage() |
| Give commands | llGiveInventory() for objects, landmarks, notecards |
Particle System Parameters (LSL)
CRITICAL: OpenSim 0.9.3.0 YEngine does NOT support symbolic particle constants like PSYS_SRC_PATTERN_EXPLODE, PSYS_PART_EMISSIVE_MASK, etc. You MUST use raw integer values. This was discovered empirically — the compiler returns "undefined constant" errors for all symbolic PSYS constants.
Valid Numeric Constants (OpenSim 0.9.3.0)
// PSYS_SRC_PATTERN values
// 1 = DROP, 2 = EXPLODE, 4 = ANGLE_CONE, 8 = ANGLE (with falloff)
integer PATTERN_EXPLODE = 2;
integer PATTERN_ANGLE_CONE = 4;
// PSYS_PART_FLAGS values
// 0 = nothing, 256 = emissive (glow)
integer PART_EMISSIVE = 256;
integer PART_SEEN = 1; // Required for visibility
integer PART_INTERP_COLOR = 16;
integer PART_INTERP_SCALE = 32;
integer PART_BOUNCE = 128;
integer PART_WIND = 512;
integer PART_FOLLOW_VELOCITY = 1024;
integer PART_TARGET = 2048;
integer PART_BOY_SOURCE = 4096;
integer PART_BOY_TARGET = 8192;
integer PART_DISABLE_TARGET = 16384;
integer PART_BLINK = 32768;
integer PART_BEAM = 65536;
integer PART_GLOW = 131072; // May not work, use 256 (emissive) instead
Working Particle Presets (Tested in OpenSim 0.9.3.0)
ALL particle parameters use raw integer values. Symbolic PSYS constant names do NOT work in OpenSim 0.9.3.0 YEngine. Every PSYS constant shown below is a raw number.
// Fire effect — ALL numeric, no symbolic names
llParticleSystem([
PSYS_PART_FLAGS, 256, // EMISSIVE
PSYS_PART_MAX_AGE, 2.0,
PSYS_PART_START_SCALE, <0.5, 0.5, 0.0>,
PSYS_PART_END_SCALE, <0.1, 0.1, 0.0>,
PSYS_PART_START_COLOR, <1.0, 0.5, 0.3>,
PSYS_PART_END_COLOR, <0.9, 0.2, 0.1>,
PSYS_PART_START_ALPHA, 1.0,
PSYS_PART_END_ALPHA, 0.0,
PSYS_SRC_PATTERN, 2, // EXPLODE
PSYS_SRC_MAX_AGE, 0,
PSYS_SRC_TEXTURE, ""
]);
// Fountain — uses pattern 4 (ANGLE_CONE)
llParticleSystem([
PSYS_PART_FLAGS, 256, // EMISSIVE
PSYS_PART_MAX_AGE, 4.0,
PSYS_PART_START_SCALE, <0.2, 0.2, 0.0>,
PSYS_PART_END_SCALE, <0.1, 0.1, 0.0>,
PSYS_PART_START_COLOR, <0.5, 0.8, 1.0>,
PSYS_PART_END_COLOR, <0.3, 0.6, 1.0>,
PSYS_PART_START_ALPHA, 1.0,
PSYS_PART_END_ALPHA, 0.0,
PSYS_SRC_PATTERN, 4, // ANGLE_CONE
PSYS_SRC_BURST_RATE, 10.0,
PSYS_SRC_BURST_PART_COUNT, 10,
PSYS_SRC_MAX_AGE, 0,
PSYS_SRC_TEXTURE, ""
]);
Particle presets summary by visual effect:
| Preset | Pattern | Rate | Count | Lifetime | Color | Emissive |
|---|
| fire | 2 (EXPLODE) | — | — | 2.0s | orange-red | 256 |
| smoke | 2 (EXPLODE) | — | — | 4.0s | gray | 0 |
| fountain | 4 (ANGLE_CONE) | 10.0 | 10 | 4.0s | blue | 256 |
| bubble | 4 (ANGLE_CONE) | 15.0 | 5 | 3.0s | light blue | 0 |
| sparkle/magic | 2 (EXPLODE) | 20.0 | 20 | 2.0s | yellow-pink | 256 |
| heart | 4 (ANGLE_CONE) | 5.0 | 5 | 3.0s | pink-red | 256 |
Rule: Never use PSYS_SRC_PATTERN_EXPLODE, PSYS_SRC_PATTERN_CONE, PSYS_PART_EMISSIVE_MASK, or any other symbolic PSYS name in OpenSim LSL. Always use raw integers.
Constants That DO NOT Work in OpenSim 0.9.3.0
These return "Error: undefined constant/function/variable":
| Invalid Constant | Reason |
|---|
PSYS_SRC_PATTERN_CONE | Use numeric 4 (ANGLE_CONE) |
PSYS_SRC_PATTERN_EXPLODE | Use numeric 2 |
PSYS_PART_EMISSIVE_MASK | Use numeric 256 |
PSYS_SRC_LIFETIME | Does not exist — remove |
PSYS_SRC_ANGLE_BEGIN | Does not exist — remove |
PSYS_SRC_ANGLE_END | Does not exist — remove |
PSYS_PART_FOUR_SOURCE_DEPTH | Does not exist — remove |
PSYS_BF_SOURCE_ALPHA | Blend function constants not supported |
PSYS_BF_ONE_MINUS_SOURCE_ALPHA | Blend function constants not supported |
PSYS_PART_START_GLOW | Does not exist — remove |
PSYS_PART_END_GLOW | Does not exist — remove |
PSYS_SRC_BURN_COLOR | Does not exist — remove |
PSYS_SRC_OUTER_TARGET_ALPHA | Does not exist — remove |
PSYS_SRC_INNER_TARGET_ALPHA | Does not exist — remove |
PSYS_SRC_OMEGA | Does not exist — remove |
Particle Color Format
OpenSim expects 3-component vectors <R, G, B> — NOT 4-component with alpha:
// WRONG — 4 components causes parsing issues:
PSYS_PART_START_COLOR, <1.0, 0.5, 0.3, 1.0>
// CORRECT — 3 components only:
PSYS_PART_START_COLOR, <1.0, 0.5, 0.3>
Particle Scale Format
Use 2D-style vectors with Z = 0.0:
// WRONG — Z component causes issues:
PSYS_PART_START_SCALE, <0.5, 0.5, 1.0>
// CORRECT — Z should be 0.0:
PSYS_PART_START_SCALE, <0.5, 0.5, 0.0>
Chat/Sound Trigger Pattern
integer listen_handle;
default {
state_entry() {
// Listen on public chat (channel 0), from anyone, with 10m range
listen_handle = llListen(0, "", NULL_KEY, "");
}
listen(integer channel, string name, key id, string message) {
// React to message
if (message == "fire") {
llParticleSystem(...); // Start fire effect
} else if (message == "stop") {
llParticleSystem([]); // Stop particles
}
}
on_rez(integer param) {
llResetScript();
}
changed(integer change) {
if (change & CHANGED_INVENTORY) {
llResetScript();
}
}
}
Animation Trigger Pattern
default {
state_entry() {
// Request permissions when attached or rezzed
llRequestPermissions(llGetOwner(), PERMISSION_TRIGGER_ANIMATION);
}
run_time_permissions(integer perm) {
if (perm & PERMISSION_TRIGGER_ANIMATION) {
llStartAnimation("my_animation");
}
}
touch_start(integer num_detected) {
if (llGetPermissions() & PERMISSION_TRIGGER_ANIMATION) {
llStartAnimation("my_animation");
}
}
}
RLV Commands (via Console + In-World)
Console RLV Commands
set rlv commandname [TRUE|FALSE|value]
set rlv sit=+AriadneTestRegion
set rlv stand=+AriadneTestRegion
set rlv showworldmap=+y
set rlv showminimap=+y
In-World RLV (OSSL)
osForceSitTarget(key avatar, vector position, rotation rot)
osSetRot(target, rotation)
osTeleportOwner(region_name, x, y, z)
Asset Type Quick Reference
| ariadne4j Type | Console | LSL Required | Notes |
|---|
type_slanimation | No | Yes | llStartAnimation() |
type_slaction | No | Yes | llSay() triggers |
type_slextfeedObject | No | Yes | OSSL functions |
type_slparticle_system | No | Yes | llParticleSystem() |
type_slsound | No | Yes | llPlaySound() |
type_slbodypart | No | Yes | Inventory item |
type_slhud | No | Yes | Attach to HUD |
type_sllandmark | No | Yes | llGiveInventory() |
type_slnotecard | No | Yes | llGiveInventory() |
type_slobject | No | Yes | llRezObject() |
type_slpackage | No | Yes | llGiveInventory() |
type_sltexture | No | Yes | Texture UUID in script |
type_slscript | Yes | No | scripts show/start/stop |
type_slanimationoverride | No | Yes | llStartAnimation() |
type_slclothing | No | Yes | Inventory item |
type_slland | Yes | No | terrain load/save |
type_slcallingcard | No | Yes | llGiveInventory() |
type_slclothes | No | Yes | Inventory item |
type_slsnapshot | No | Yes | Viewer capture |
type_sllslease | Yes | No | land show |
type_slassetcontainer | No | Yes | Inventory folder |
type_slensemble | No | Yes | Linked set |
type_slgesture | No | Yes | llDialog() + animation |
type_slhydra | No | Yes | Multi-prim object |
type_sllink | No | Yes | llCreateLink() |
type_slmeat | No | Yes | Inventory item |
type_slskin | No | Yes | Inventory item |
type_sltattoo | No | Yes | Inventory item |
type_slwormhole | No | Yes | Teleport object script |
Workflow for Testing All Types
- Use viewer to create initial prims and upload assets
- Use console for:
- Terrain operations (save/load)
- Script control (start/stop)
- User management
- World state queries
- Use LSL scripts in-world for:
- Object creation (
llRezObject)
- Particle systems (
llParticleSystem)
- Animation triggers
- Chat/sound triggers
- Inventory operations (
llGiveInventory)
- RLV restrictions
Console Test Script
"""Console test commands for OpenSim - items that work without LSL."""
import subprocess
import time
COMMANDS = [
("show version", "Check OpenSim version"),
("show uptime", "Server uptime"),
("show users", "Connected avatars"),
("show regions", "Region list"),
("show stats", "Performance stats"),
("scripts show", "Running scripts"),
("terrain show 128 128", "Terrain height at point"),
("config show", "Configuration dump"),
("land show", "Parcel information"),
("get log level", "Current log level"),
]
def run_command(cmd):
result = subprocess.run(
["curl", "-s", "-X", "POST", "http://127.0.0.1:9000/StartSession/",
"-d", "USER=Test", "-d", "PASS=secret"],
capture_output=True, text=True
)
session = result.stdout.split("<SessionID>")[1].split("<")[0]
subprocess.run(
["curl", "-s", "-X", "POST", "http://127.0.0.1:9000/SessionCommand/",
"-d", f"ID={session}", "-d", f"COMMAND={cmd}"]
)
time.sleep(0.5)
result = subprocess.run(
["curl", "-s", f"http://127.0.0.1:9000/ReadResponses/{session}/"],
capture_output=True, text=True
)
subprocess.run(
["curl", "-s", "-X", "POST", "http://127.0.0.1:9000/CloseSession/",
"-d", f"ID={session}"]
)
return result.stdout
for cmd, desc in COMMANDS:
print(f"\n{'='*60}")
print(f"CMD: {cmd}")
print(f"DESC: {desc}")
print(run_command(cmd)[:500])
LSL Script Template for Object Creation
// opensim-create-prim.lsl
// Rez a configurable prim from inventory
string PRIM_NAME = "TemplatePrim";
vector PRIM_COLOR = <0.55, 0.27, 0.07>; // brown
vector PRIM_SCALE = <0.5, 0.5, 2.0>; // 2m tall cylinder
default {
state_entry() {
llOwnerSay("Object creation ready. Touch to rez.");
}
touch_start(integer num_detected) {
// Rez object from inventory
vector pos = llGetPos() + <0, 0, 1>; // 1m above current position
llRezObject(PRIM_NAME, pos, <0,0,0>, ZERO_ROTATION, 0);
}
on_rez(integer param) {
// Configure the rezzed object
llSetScale(PRIM_SCALE);
llSetColor(PRIM_COLOR, ALL_SIDES);
}
}
Script Organization by Prim
Recommended: 2 Prims (Clean Separation)
Prim 1: "Controller" — handle control scripts only
Scripts: master-controller.lsl
trigger-v2.lsl (animations/sounds)
give-v1.lsl (give items)
particle-v1.lsl (particle effects)
media-v1.lsl (media URLs)
Prim 2: "Rezzer" — object rezzing only
Scripts: rezzer-v3-fixed.lsl
Assets: template objects inside this prim
All-In-One Prim (Chat-Only Scripts)
If you want ALL scripts in ONE prim, use master-controller.lsl only. It combines all functionality via chat commands — no touch events, so no conflicts.
Do NOT mix touch-based scripts in the same prim. Each touch event fires all scripts simultaneously, causing menu confusion. For example, trigger-v2.lsl + rezzer-v3-fixed.lsl in the same prim causes the first sound menu item "List Anim" to be misread as an animation name.
Scripts to Skip
| Script | Reason |
|---|
trigger-v1.lsl | Replaced by v2 |
rlv-v1.lsl | Incomplete |
anim-test-v2.lsl | One-time test |
test-animation.lsl | One-time test |
Error: "undefined constant/function/variable ATTACHMENT_KEY"
Symptom: (707,25) Error: undefined constant/function/variable ATTACHMENT_KEY — same on line 723
Root cause: Two bugs in master-controller.lsl:
ATTACHMENT_KEY was never declared as a global
llAttachAvatarAssets() does not exist in LSL or OSSL — it was invented code
Confirmed fix (2026-05-09): Added integer ATTACHMENT_KEY = 7; global, replaced both calls with osForceAttachToAvatarFromInventory(found, ATTACHMENT_KEY). OpenSim.log at 20:10:11 showed the errors; after patch, no new errors appear. HTTP cmd=wear&arg=Pants and cmd=bodypart return OK confirmed.
// WRONG (non-existent function, args also reversed):
llAttachAvatarAssets(ATTACHMENT_KEY, found);
// CORRECT — item name FIRST, attachment point SECOND:
osForceAttachToAvatarFromInventory(found, ATTACHMENT_KEY);
Rule: OpenSim has no llAttachAvatarAssets. Always use osForceAttachToAvatarFromInventory(string itemName, integer attachmentPoint). The item name goes first.
Channel Assignments (Non-Overlapping)
If running multiple scripts in the same prim, assign non-overlapping channels:
| Script | Command Ch | Menu Ch | Menu Sub-Channels |
|---|
| master-controller.lsl (unified v2) | 990 | -9000 | -9001 to -9006 |
| give-v1.lsl | 996 | -8996 | -8997 to -8999 |
| trigger-v2.lsl | 998 | -9100 | -9101 to -9104 |
| media-v1.lsl | 994 | -8994 | -8995 to -8997 |
| rezzer-v3-fixed.lsl | 999 | -8999 | (reserved) |
Recommended: Use master-controller.lsl alone — it integrates ALL functionality on channel 990.
In-World LSL Script Library
Ready-to-deploy scripts in scripts/ subdirectory:
| Script | Lines | Channel | Status |
|---|
master-controller.lsl | 800+ | 990 | PRIMARY — unified v2 combines rez+anim+sound+give+media+HTTP |
remote-test-controller.lsl | 310 | HTTP | Receives remote HTTP commands via osRequestURL(); supports anim/gesture/particle/texture/wear/stop_all/ping |
remote-http-test.py | Python | — | HTTP test sequencer with 12-second pauses between types; expanded 2026-05-09: clothing (5 items), bodyparts (6 items), utility commands (stop/partoff/status/list); run: python remote-http-test.py <http-url> |
rezzer-v3-fixed.lsl | 273 | 999 | Works (verified) |
trigger-v1.lsl | 345 | 998 | Fixed |
particle-v1.lsl | 657 | 997 | Fixed |
give-v1.lsl | 396 | 996 | Fixed |
rlv-v1.lsl | 298 | 995 | Fixed |
media-v1.lsl | 486 | 994 | Fixed |
anim-test-v2.lsl | ~80 | N/A | Tests library animations (handshake, tpose, wave) |
Copying Scripts In-World
- Rez a prim in OpenSim
- Open the prim's Contents tab in viewer
- Create a new script (or paste from file)
- For rezzer: add template objects to prim's inventory first
- For others: add required assets (sounds, textures) to prim's inventory
master-controller.lsl v2 Commands (channel 990)
Unified controller — no conflicts between sub-systems:
!rez <name> - Rez object from inventory
!anim <name> - Play animation from inventory
!stopanim - Stop current animation
!sound <name> - Play sound from inventory
!give <item> - Give item to self
!giveall <item> - Give item to all nearby avatars
!audio <name> - Play audio
!stopaudio - Stop audio
!volume <0-1> - Set volume
!image <name> - Set texture from inventory
!video <url> - Set video/media URL
!preset <name> - Apply particle preset
!partoff - Disable particles
!menu - Show dialog menu
!status - Show all settings
!help - Show help
HTTP remote trigger: the script calls llRequestURL() on start to receive external HTTP POST requests.
llRequestURL() / osRequestURL() denied in standalone mode
Chat-only fallback (works in all configs):
!anim <name> - Play animation from inventory
!stopanim - Stop current animation
!status - Show all settings (HTTP URL slot = empty if denied)
External trigger via HTTP (grid-only, not standalone):
curl -X POST <http-url> -d 'cmd=anim&arg=handshake'
curl -X POST <http-url> -d 'cmd=wear&arg=Pants'
curl -X POST <http-url> -d 'cmd=bodypart&arg=Shape'
curl -X POST <http-url> -d 'cmd=particle&arg=fire'
curl -X POST <http-url> -d 'cmd=status'
curl -X POST <http-url> -d 'cmd=stop'
URL changes on every script reset — osRequestURL() returns a new UUID path each time. Pass the current URL on the command line; there is no persistent URL across resets.
Common Chat Commands (per script — only if NOT using master-controller)
trigger-v1 (ch 998):
!anim <name> - Play animation from inventory
!sound <name> - Play sound
!say <text> / !shout <text> / !whisper <text>
!im <msg> - Send instant message
!channel <n> <msg> - Send on channel N
particle-v1 (ch 997):
!preset <name> - Apply preset (fire, smoke, fountain, sparkle, magic, etc. — 18 presets)
!on / !off - Enable/disable particles
!list - List all 18 presets
Particle Presets (particle-v1.lsl)
18 built-in presets via !997 preset <name>:
| Category | Presets |
|---|
| Fire | fire, flame, ember, smoke, smoke_light |
| Water | fountain, bubble, rain |
| Nature | snow, leaf, cloud, dust |
| Magic | sparkle, magic, star, heart, aura, vortex |
Each preset configures: pattern, burst rate/count, particle lifetime, color/alpha interpolation, scale interpolation, texture, flags (emissive, wind, bounce, trail), and omega (spin).
OpenSim LSL Compatibility Pitfalls
CRITICAL: OpenSim LSL differs from standard LSL. Common errors:
| Feature | Standard LSL | OpenSim | Workaround |
|---|
break in loops | Valid | Invalid | Use i = count; to exit loop or restructure with if |
llClamp(x,min,max) | Valid | Invalid | Manual: if (x < min) x = min; if (x > max) x = max; |
llGetInventoryType(name, type) | 2 args | Only 1 arg | Remove second arg: llGetInventoryType(name) |
llStringTrim(string) | Valid | Invalid | Use bare string comparison if (s != ""), or write manual trim helper |
do-while loops | Valid | May vary | Use while loops only |
continue | Valid | Invalid | Restructure with if |
Loop Exit Pattern (no break)
// WRONG - 'break' doesn't exist in OpenSim LSL:
for (i = 0; i < count; i++) {
if (found) break; // ERROR!
}
// CORRECT - exit by setting counter:
integer found = -1;
for (i = 0; i < count; i++) {
if (llGetInventoryName(INVENTORY_OBJECT, i) == target) {
found = i;
i = count; // Exit loop
}
}
Manual Clamp Pattern (no llClamp)
// WRONG - llClamp doesn't exist:
float val = llClamp(value, 0.0, 1.0); // ERROR!
// CORRECT:
float val = value;
if (val < 0.0) val = 0.0;
if (val > 1.0) val = 1.0;
Inventory Functions
// WRONG - 2 args:
integer type = llGetInventoryType(name, INVENTORY_OBJECT); // ERROR!
// CORRECT - 1 arg only:
integer type = llGetInventoryType(name);
Manual Trim Helper (no llStringTrim)
// Custom trim function - strips leading/trailing whitespace
string strTrim(string s) {
integer start = 0;
integer end = llStringLength(s) - 1;
while (start <= end && llSubStringIndex(" \t\n\r", llGetSubString(s, start, start)) >= 0) {
start++;
}
while (end >= start && llSubStringIndex(" \t\n\r", llGetSubString(s, end, end)) >= 0) {
end--;
}
if (start > end) return "";
return llGetSubString(s, start, end);
}
// Usage: for comparisons, often you can skip trim entirely:
// if (msg != "") // works without trim
Multi-Page Dialog Menus (menu channel pattern)
When using llDialog() for sub-menus, each dialog uses a different channel. You must listen on ALL menu channels:
integer gMainChannel = 999;
integer gMenuChannel = -8999; // base for sub-menus
default {
state_entry() {
// Listen on main channel
llListen(gMainChannel, "", NULL_KEY, "");
// Listen on base menu channel AND all sub-menu channels (+1 through +6)
llListen(gMenuChannel, "", NULL_KEY, "");
llListen(gMenuChannel + 1, "", NULL_KEY, "");
llListen(gMenuChannel + 2, "", NULL_KEY, "");
llListen(gMenuChannel + 3, "", NULL_KEY, "");
llListen(gMenuChannel + 4, "", NULL_KEY, "");
llListen(gMenuChannel + 5, "", NULL_KEY, "");
llListen(gMenuChannel + 6, "", NULL_KEY, "");
}
listen(integer ch, string name, key id, string msg) {
// Route all menu channel responses to same handler
if (ch >= gMenuChannel && ch <= gMenuChannel + 6) {
handleMenu(id, msg); // same handler for all sub-menus
} else {
handleCommand(msg, id); // main channel commands
}
}
}
Key: the listen event must distinguish menu channels from command channels. Use ch >= gMenuChannel && ch <= gMenuChannel + N to catch all sub-menu responses.
### Alternative: Use OS_ prefixed OSSL Functions
OpenSim extends LSL with OSSL functions. Check availability:
```lsl
osGetNotecard(string name) // Read notecard content
osSetPenColor(string color) // Set drawing color
osSetDynamicTextureData(...) // Create dynamic textures
osTeleportOwner(...) // Teleport functions
OpenSim Version Info
- OpenSim: 0.9.3.0 Nessie Release (SIM-0.3/0.8)
- Console: REST on port 9000, user=Test, pass=secret
- Script Engine: YEngine (LSL + OSSL)
- Physics: BulletSim v1.0, 2.84
IAR Builder (Inventory Archive)
Create IAR archives from directories of assets for importing into OpenSim:
| Script | Purpose | Usage |
|---|
scripts/iar_builder.py | Create + import IAR files | python iar_builder.py create output.iar --assets type:/path --assets type:/path |
scripts/bvh_to_anim.py | Single BVH to .anim | python bvh_to_anim.py input.bvh output.anim [--fps 30] |
scripts/batch_bvh_to_anim.py | Batch BVH directory | python batch_bvh_to_anim.py /input/dir /output/dir [--fps 30] |
IAR Builder Usage
python iar_builder.py scan animation /path/to/files --recursive
python iar_builder.py create output.iar \
--assets animation:/path/to/anims \
--assets sound:/path/to/sounds \
--assets texture:/path/to/textures \
--inventory-root "My Assets"
python iar_builder.py list output.iar
python iar_builder.py import output.iar FirstName LastName secret \
--url http://127.0.0.1:9000 \
--console-user Test --console-pass secret
Supported asset types: animation (.anim, .bvh), sound (.wav, .ogg, .mp3), texture (.jp2, .png, .jpg, .tga), notecard (.txt, .lnc), gesture (.gesture), script (.lsl), object (.xml, .prim, .scene).
BVH Conversion Notes
BVH files cannot be directly uploaded via console — the viewer handles BVH upload and converts internally. The Python converters produce .anim files but the rotation mapping has NOT been validated against actual SL skeleton structure. For production-quality animations, use viewer upload. The Python path is useful for batch testing.
llSensor Type Parameter
// WRONG - OBJECT and OBJECT_TYPE are not valid LSL constants:
llSensor("", OBJECT, 10.0, PI); // Error: undefined 'OBJECT'
llSensor("", OBJECT_TYPE, 10.0, PI); // Error: undefined 'OBJECT_TYPE'
// CORRECT - use numeric type or empty string:
// Type 8 = AGENT, Type 4 = OBJECT, Type 0 = ALL
llSensor("", "", 8, 10.0, PI); // Find agents within 10m
llSensor("", "", 4, 10.0, PI); // Find objects within 10m
llSensor("targetname", "", 0, 20.0, TWO_PI); // Find by name, all types
// For RLV "Force Sit" - skip llSensor entirely, use osForceSitTarget() instead
Menu Handler Channel Routing Pattern
The listen event must route sub-menu channel responses — NOT the menu handler itself:
// CORRECT - routing happens in listen event:
listen(integer ch, string name, key id, string msg) {
if (ch == gMenuChannel) {
handleMenu(id, msg); // Main menu choices
} else if (ch >= gMenuChannel + 1 && ch <= gMenuChannel + 6) {
handleSubMenu(id, msg); // Sub-menu responses (no 'ch' reference!)
} else {
handleCommand(msg, id); // Main command channel
}
}
// WRONG - handleMenu must NOT reference 'ch' (not in scope):
handleMenu(key user, string choice) {
// ...menu button handling...
// Do NOT check ch here - handleMenu doesn't receive 'ch'
}
// WRONG - listen handler passing 'ch' to handleMenu:
listen(integer ch, ...) {
handleMenu(id, msg); // Pass msg only, not ch!
// handleMenu can't see 'ch' - causes "undefined name ch" error
}
Rule: handleMenu(user, choice) takes 2 params. listen passes id and msg — never ch to handleMenu. Sub-menu channel routing is handled entirely within listen via the ch >= gMenuChannel+N check.
User Account Name Format (CRITICAL for IAR Import)
OpenSim user accounts use FirstName LastName as a single string with a space.
| Field | Value |
|---|
| First Name | sanchorelaxo |
| Last Name | Algoma |
| Full name | sanchorelaxo Algoma (space-separated) |
WRONG formats:
sancho telaxo — doesn't exist
sanchorelaxo alone — incomplete
When importing IAR via console:
load iar sanchorelaxo Algoma /Inventory/path password archive.iar
When using iar_builder.py:
python iar_builder.py import archive.iar sanchorelaxo Algoma password
OpenSim LSL Compatibility Pitfalls
CRITICAL: OpenSim LSL differs from standard LSL. Common errors:
| Error | Cause | Fix |
|---|
(line) Error: undefined name ch | Menu handler trying to reference 'ch' variable | handleMenu(user, choice) doesn't receive 'ch'; route via listen |
(line) Error: ... OBJECT | llSensor with invalid constant | Use numeric type: llSensor("", "", 8, ...) for AGENT |
(line) Error: ... OBJECT_TYPE | llSensor with invalid constant | Use empty string or numeric: llSensor("", "", 4, ...) for OBJECT |
(line) Error: ... break | break keyword doesn't exist | Exit loop via i = count; |
(line) Error: ... llClamp | llClamp() doesn't exist | Manual clamp or define fClamp() |
(line) Error: ... llGetInventoryType(...,...) | 2 args not supported | Use 1 arg: llGetInventoryType(name) |
(line) Error: undefined name llStringTrim | Function doesn't exist | Use bare string checks or write strTrim() |
llDialog: Button label cannot be blank | llGetInventoryName() returns "" for some slots; blank entries in button list | Always filter empty names when building inventory lists: if (name == "") continue; |
URL_REQUEST_DENIED on llRequestURL() | ExternalHostNameForLSL in [Network] does NOT work — URL MODULE ignores it | Only BaseURL in [Startup] section fixes it — not [Network] |
(line) Error: undefined PSYS_SRC_PATTERN_CONE | Symbolic pattern constant not supported | Use numeric 4 |
(line) Error: undefined PSYS_SRC_PATTERN_EXPLODE | Symbolic pattern constant not supported | Use numeric 2 |
(line) Error: undefined PSYS_SRC_LIFETIME | Parameter not supported | Remove from particle list |
(line) Error: undefined PSYS_PART_EMISSIVE_MASK | Symbolic flag not supported | Use numeric 256 |
(line) Error: undefined name <var> | Menu handler passing ch to handleMenu() which doesn't accept it | handleMenu(user, choice) takes 2 params only; route sub-menu channels via listen event with if (ch >= gMenuChannel + 1 && ch <= gMenuChannel + N) |
Skill Files
Scripts (scripts/):
iar_builder.py - Inventory archive builder + importer (create, list, import)
batch_bvh_to_anim.py - Batch BVH converter (ThreadPoolExecutor, skips _vti_cnf dirs)
bvh_to_anim.py - Single file BVH converter
remote-http-test.py - HTTP test sequencer with 12-second pauses between asset types. Run: python remote-http-test.py <http-url> (from master-controller.lsl touch/reset). Tests: animations → 12s pause → gestures → 12s pause → particles (3s each) → 12s pause → textures → 12s pause → clothing → 12s pause → body parts.
master-controller.lsl - Primary in-world controller (channel 990). HTTP-in via osRequestURL() on startup (url announced on channel -800). Form-encoded POST: cmd=anim&arg=<name>, cmd=gesture&arg=<name>, cmd=texture&arg=<name>, cmd=particle&arg=<preset>, cmd=wear&arg=<clothing>, cmd=bodypart&arg=<bodypart>, cmd=stop, cmd=partoff, cmd=ping, cmd=status, cmd=list. Supports optional cb=<callback-url> for async response callbacks.
OpenSim Library Animations
To test library animations, use anim-test-v2.lsl which calls llStartAnimation("handshake"), llStartAnimation("tpose"), and llStartAnimation("wave"). Library animations like handshake, tpose, tpose2, wave are built into OpenSim and available without uploading.
Skill Files
References (references/):
lsl-script-fixes.md - Session fixes: continue replacement, empty button labels, pagination, script conflict by prim
iar-builder-reference.md - IAR format details, asset type codes, console commands, script usage
opensim-library-assets.md - Built-in library animations, gestures, body parts, clothing
opensim-library-animations.md - Library animation list, avatar UUID, user name format, script error diagnostics
remote-http-test-pattern.md - HTTP remote trigger pattern: osRequestURL controller script + Python test sequencer, deployment steps, particle/animation debugging