← Paracosm·Docs/Handbook
Contents · Scripts (LSL through WorldBridge)

Scripts (LSL through WorldBridge)

Scripts are how the world lives. WorldBridge set_script attaches LSL text to an object's root prim and starts it under YEngine, the region's script engine, bypassing the permission gate that stops a non-present owner from adding a script. Most builders in tools/ generate their LSL as Python strings and lay it with set_script.

Laying scripts

1. One behaviour, one script name

set_script REPLACES a script of the same name and ADDS a script with a different name alongside the old one. Two scripts that both read-modify-write the same property race and clobber each other, silently; the last to run wins.

2. started=true is not the compile verdict

set_script answers started=true even when YEngine rejects the script. The compile error goes to the server's bin/OpenSim.log, logged BEFORE WorldBridge's own set_script line, and nothing runs. A script that "does nothing" is a compile error until shown otherwise.

3. Templated scripts: fill every placeholder

Unfilled %PLACEHOLDER%s are the usual compile error ("invalid operand token" at the column of the %). In Python, a + b + c.replace(...) fills only c; bracket the whole concatenation before the replace chain. Insert LSL that uses % (modulo) with .replace, never with Python %-formatting.

4. Write if/else, not ?:

Standard LSL has no ternary. YEngine accepted one in a declaration but rejected llSetAlpha(flag ? 0.0 : 1.0, ...).

5. A touch handler added to an existing object needs its click flags resent

The viewer may not offer Touch until the object's flags are resent: call llSetClickAction(CLICK_ACTION_TOUCH) in state_entry (it also makes a left-click a touch).

6. One-shot scripts leave the look and remove themselves

A script that sets faces, status or parameters and ends with llRemoveInventory(llGetScriptName()) leaves its effect and no running script. tools/skin_cottage.py, tools/phantomize.py, tools/mask_cutoffs.py all use it.

Time and shared state

7. Use a shared clock to move separate scripts together

llGetGMTclock() (float seconds) is the same in every script. llGetTime() starts at each script's own reset, so phases drift apart. llGetUnixTime() is shared but whole seconds. For sub-second shared phase, the build used the fraction from llGetTimestamp.

8. Durable memory lives in the object description

llGetObjectDesc / llSetObjectDesc survive script resets AND region restarts.

9. One controller owns each accumulating state

Weather memory (snow depth, wetness, ice, icicles) belongs in ONE controller that integrates it on the shared clocks, persists it in its description, and broadcasts each value on its own channel. Tune the rates with a Python simulation of the real clocks first.

10. The island's time of day in a script

The sky's time of day is the region's environment day, and OpenSim's LSL reads it: llGetRegionDayLength() and llGetRegionDayOffset(); fraction = ((llGetUnixTime() + offset) % length) / length, 0.5 = midday. llGetTimeOfDay is the legacy 4-hour cycle and does NOT follow the environment day. llGetSunDirection().z tells day from night.

11. Do not switch many things at one threshold

Lights that switch at the same sun height all switch together, which reads as a switch. Give each its own threshold and the dusk becomes a keeper going round (dawn reverses it for free).

A world that notices you

12. Ease toward a visitor, then stop

llSensorRepeat("", NULL_KEY, AGENT, radius, PI, 1.5) finds the nearest visitor; ease a welcome toward 1 - distance/radius on the timer so things swell as you approach and settle after you leave, instead of snapping. Stop the timer once settled so an unvisited thing costs nothing.

13. One-shot reactions need hysteresis

Forget the visitor only after about 20 s out of range, or the edge of the radius rings a greeting over and over.

14. Carry every change in size and colour, not glow alone

Glow is a bloom effect the viewer dims or drops at modest graphics settings.

15. An NPC is an agent for sensors

Stand one somewhere (npc_create) to test anything presence-gated without a person.

Talking to scripts

16. Talk to scripts with chat_send (or wb.region_say)

WorldBridge chat_send delivers through the world-comm module, as an object's llSay is, so scripts' llListen hear it (scripts, default true), and it speaks as the estate owner (from_uuid), so owner-checking listeners accept it. wb.region_say(channel, msg) is chat_send with region=True (as llRegionSay).

17. Read a script's state back through its description or name

list returns each object's description as its shape field and its name as name. A script that writes its last action or its computed values into llSetObjectDesc (or its object name) is readable with no console. (Object llSay is not in WorldBridge's chat log, and console show object output goes to the console, not the log.)

18. Messages between objects

Objects talk on private channels (llRegionSay / llListen). Replacing an object does not break this: messages reach the new one by channel as before. Two creatures sharing perches can announce the perch they take on a private channel.

Testing slow and rare behaviour

19. Test a slow cycle with a fast copy on one prim

A 20-minute storm or a 4-day year cannot be watched. Deploy a FAST-period copy to ONE prim, watch the driven property swing (colour and size via inspect / list), check it holds across a script re-set, then restore the real one.

20. Test a gated behaviour by moving its threshold

A weather-gated thing (rain, a rainbow, a strike) may be 40 minutes from its window. Re-lay its script with the thresholds moved (a harness), watch it, then put the real thresholds back. Several builders take a command for this (for example python tools/build_thunder.py strike <class> and python tools/build_shipbell.py strike 8).

21. Behavioural readback proves a timer is running

inspect a flickering flame's colour twice; if it changed, the timer is firing. Position readback of a fast re-scripted prim is too noisy to measure a drift rate.

Limits you will meet

22. llSetRegionPos and PRIM_POSITION will not put a prim's centre below the ground

A mesh spawned with its centre under the terrain (a reed clump in the shallows) is lifted on its first scripted move (11 cm here). An emitter that should sit at water level over land cannot get there; search nearby for open water instead.

23. llWind is not a wind to build on

It is a weak random field (0.1-1.2 m/s), each 64 m patch its own direction, reshuffled about every 15 s. Give the region ONE wind from a shared-clock formula and make everything read it (weather_common.WIND_LSL).

24. Particles exist only in the viewer

llParticleSystem makes fire, sparks, spray, mist and rain; verify it by looking in the viewer. An emitter prim is a tiny sphere set to alpha 0. See weather-and-light.md.