원클릭으로
opensim-asset-creation
OpenSimulator object creation workflow - console vs. LSL approaches for all ariadne4j asset types
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
OpenSimulator object creation workflow - console vs. LSL approaches for all ariadne4j asset types
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | opensim-asset-creation |
| description | OpenSimulator object creation workflow - console vs. LSL approaches for all ariadne4j asset types |
| version | 1.0.0 |
| category | gaming |
Documents what's possible via OpenSim console vs. what requires in-world LSL/OSSL scripting.
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 |
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.
To create new objects, you must:
llRezObject() to create new objects from inventorydefault {
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(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 |
| 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 |
| 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 |
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.
// 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
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.
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 |
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>
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>
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();
}
}
}
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");
}
}
}
# Set RLV restriction
set rlv commandname [TRUE|FALSE|value]
# Common RLV restrictions
set rlv sit=+AriadneTestRegion # Allow sitting
set rlv stand=+AriadneTestRegion # Allow standing
set rlv showworldmap=+y # Allow world map
set rlv showminimap=+y # Allow minimap
osForceSitTarget(key avatar, vector position, rotation rot)
osSetRot(target, rotation)
osTeleportOwner(region_name, x, y, z)
| 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 |
llRezObject)llParticleSystem)llGiveInventory)#!/usr/bin/env python3
"""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])
// 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);
}
}
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
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.
| 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 |
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 globalllAttachAvatarAssets() does not exist in LSL or OSSL — it was invented codeConfirmed 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.
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.
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) |
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
llRequestURL() in state_entry, receives URL_REQUEST_DENIED, and log shows "ExternalHostNameForLSL not defined in configuration, HTTP listener for LSL disabled"ExternalHostNameForLSL from the [Network] config section. If [URLAccessModule] section (line ~696 in OpenSim.ini) is used instead, the include order may cause the URL module to not find the value during initialization.[Network]
ExternalHostNameForLSL = "127.0.0.1"
The [Network] section already contains console/auth settings (ConsoleUser, ConsolePass, console_port, http_listener_port). Adding ExternalHostNameForLSL there works — this is what UrlModule.cs reads at line 134 (config.Configs["Network"].GetString("ExternalHostNameForLSL", null)).[Network] section that OVERRIDES the one in OpenSim.ini. Later includes replace earlier ones.OutboundDisallowForUserScriptsExcept = "127.0.0.1:8080|127.0.0.1:9000"
This allows scripts to make HTTP requests TO the REST console (MCP bridge, etc.).llSleep(2.0) before first llRequestURL() call.URL_REQUEST_DENIED, wait 5s and call llRequestURL() again.https://opensimulator.dev/ (not opensimulator.org). The domain migrated.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.
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 Nparticle-v1 (ch 997):
!preset <name> - Apply preset (fire, smoke, fountain, sparkle, magic, etc. — 18 presets)!on / !off - Enable/disable particles!list - List all 18 presets18 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).
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 |
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
}
}
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;
// WRONG - 2 args:
integer type = llGetInventoryType(name, INVENTORY_OBJECT); // ERROR!
// CORRECT - 1 arg only:
integer type = llGetInventoryType(name);
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
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
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] |
# Scan for assets
python iar_builder.py scan animation /path/to/files --recursive
# Create IAR from multiple asset directories
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"
# List IAR contents
python iar_builder.py list output.iar
# Import into running OpenSim
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 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.
// 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
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.
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 existsanchorelaxo alone — incompleteWhen 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
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) |
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 converterremote-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.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.
References (references/):
lsl-script-fixes.md - Session fixes: continue replacement, empty button labels, pagination, script conflict by primiar-builder-reference.md - IAR format details, asset type codes, console commands, script usageopensim-library-assets.md - Built-in library animations, gestures, body parts, clothingopensim-library-animations.md - Library animation list, avatar UUID, user name format, script error diagnosticsremote-http-test-pattern.md - HTTP remote trigger pattern: osRequestURL controller script + Python test sequencer, deployment steps, particle/animation debugging