Events, dialogs & permissions

Receive a menu reply, wait for an animation grant, and keep each script execution separate.

Receive dialog replies

llDialog displays buttons; clicking one sends its label to a listener on the dialog's channel. Register the listener before opening the menu. NULL_KEY in llListen's avatar filter means any sender. An explicit avatar key restricts the reply to that avatar.

This complete LSL example serves one person at a time using one listener registered at startup. A new touch replaces the active visitor; a reply or a 30-second timeout clears that visitor. The listener stays registered until reset or termination. It uses an example channel: choose a channel appropriate for your object when combining scripts. A channel is not a substitute for checking the sender.

LSL

The example is tested through JavaScript and TypeScript generation and sandbox execution. Its generated TypeScript also passes type-checking against the SDK and the editor declarations. If an older editor reports missing Agent.position or WorldAPI.dialog, update the editor and transpile again. The example discards llListen's return value to avoid a separate integer/listener-handle type mismatch.

integer channel = -91342;
key visitor = NULL_KEY;

default
{
    state_entry()
    {
        llListen(channel, "", NULL_KEY, "");
    }

    touch_start(integer count)
    {
        visitor = llDetectedKey(0);
        llSetTimerEvent(30.0);
        llDialog(visitor, "Choose an action", ["Hello", "Close"], channel);
    }

    listen(integer ch, string name, key id, string message)
    {
        if (visitor == NULL_KEY || id != visitor) return;
        if (message != "Hello" && message != "Close") return;
        visitor = NULL_KEY;
        llSetTimerEvent(0.0);
        if (message == "Hello") llSay(0, "Hello!");
    }

    timer()
    {
        visitor = NULL_KEY;
        llSetTimerEvent(0.0);
    }
}

Closing or ignoring the panel does not send a button label to this script. The timeout clears the active visitor; it does not dismiss the viewer's panel. A reply is ignored when no visitor is active. If the same person opens another menu, an older matching reply cannot be distinguished on this shared channel; use per-interaction channels for that stronger isolation.

The current World viewer displays script dialogs at the top right without blocking movement. Buttons form rows of three from the bottom, and unanswered dialogs can appear in a compact backlog. If your script calls llDialog again after handling a reply, the menu opens again. That is script-authored behavior, not a failed close.

One event at a time per script

An event handler finishes before the same script starts its next queued event, including while a translated host call is awaiting a result. Other scripts can continue during that wait. Keep handlers short: a slow handler delays its own timer, touch and listen events.

This is event ordering, not a promise of unlimited buffering or a global ordering across objects. A reset replaces the execution; locally queued work from the old execution must not resume inside the replacement.

Request animation permission first

Use llRequestPermissions with PERMISSION_TRIGGER_ANIMATION, then check the bit in run_time_permissions before starting or stopping an animation. Do not start immediately after making the request.

With the correlation-enabled SDK and World host, each reply is matched to its original request and execution. The handler still receives only the mask; llGetPermissionsKey() identifies the avatar for that accepted reply. Late replies cannot be mistaken for a newer avatar's request, and replies from before a reset are rejected.

A script still holds one permission grant at a time. Serialize visitor interactions or use separate scripts when several visitors need ongoing animation control. At most 64 unanswered requests are retained; requesting another expires the oldest, whose eventual reply is ignored.

Requires a paired runtime update. The SDK and World host must both support request correlation. Older deployed clients still need one outstanding request per script; a timeout or reset alone does not make their late replies safe. Updating this guide does not update World. A new SDK rejects replies from an old host that omits correlation.

llStartAnimation and llStopAnimation act on the avatar whose permission the script holds. The viewer running the script is not automatically the animation target. Put named animation assets in the object's inventory; a missing asset still needs to be resolved.

llGetAnimationList queries known active animation asset IDs for the requested avatar. A query does not grant animation permission, and its result is not a list of inventory names.

Current limits
Full non-seated consent prompts and shared seat occupancy are still being completed. Do not treat today's permission behavior as completed SL consent parity. A script should still request permission and handle a missing grant. Imported animation loop metadata also has an open playback discrepancy; successfully starting a clip does not prove it will loop correctly.

Current permissions and queued answers (ADR-210)

The upcoming paired consent runtime keeps permission getters current even while a handler is waiting. A queued run_time_permissions argument describes that answer; a newer decision or Stop action may have changed what the script is allowed to do by the time it runs. Check llGetPermissions() and llGetPermissionsKey() against the intended avatar before a permissioned action.

Closing a permission prompt leaves it unanswered and grants nothing. It is different from choosing No. Revocation updates permission state without inventing another run_time_permissions answer. A stopped script does not run a permission callback; a full event queue can also lose a callback, with a diagnostic. State still updates.

This behavior requires World to opt into the ADR-210 runtime and enforce its matching broker contract. It is under paired integration; the older correlation-only mode above retains its previous callback snapshots. Saved consent for attachments is a later feature, not implemented by this runtime update.

Reset and execution cleanup

A reset starts a fresh execution. The old execution's listeners, timers, queued events and tracked execution-scoped resources are released, and its recorded animation grant is revoked. A replacement script must establish its listeners, timers and permissions again. Incarnation-tagged API responses from an old execution are rejected by the replacement.

In the correlation-enabled runtime, permission replies also carry execution identity, and World cancels queued permission decisions when their execution ends. Other delayed host events, including dataserver replies, can still reach a replacement unless the host cancels or fences them. Where an event supplies a query ID, accept only the ID of your current request. The permission fix does not fence every host event.

Reset cannot cancel or roll back an external operation already admitted by a host service. An in-flight persistent write or creation may still finish; that service needs its own execution checks to prevent a stale commit. Do not use reset as a transaction rollback.

This does not undo every visible effect or delete objects the script created. Full saved VM-state restoration across take, rez and relog is a separate capability; fresh-source recovery is not restoration of local variables or a suspended handler.

Host integration: current event handlers

The handler-presence runtime exposes adapter.getHandlerPresence(containerId, ["onTouchStart", "onTouch", "onTouchEnd"]). It returns present, absent, or unknown for that container's live executions. World queries the actual touch dispatch target (the linkset root or unlinked object), so an unrelated child's handlers do not suppress an inferred sit. Explicit click actions and installed click handlers take precedence.

The snapshot follows the current LSL state and global TypeScript handlers. Paused, terminated and detached terminal orphans do not count. Recoverable event errors retain their current handler snapshots because later events still run. Failed executions without a snapshot remain unknown. Loading executions, older workers without snapshots, executing handlers, pending host calls, delays or paced dataserver deliveries, and accessor- or Proxy-defined handlers remain unknown; unknown must not be treated as no touch handler. State transitions publish before the new state's awaited entry body. Detached delay/host-call continuations keep the list unknown until their callback turn settles and refreshes it. Reset and worker recovery invalidate the previous execution's snapshot.

Sandbox and host-created Proxies are tracked by identity so collecting handler metadata does not invoke their reflective traps.

This is routing metadata, not consent or permission authority. It requires the matching host library and rebuilt worker; it does not add a Reset button or by itself change World's click behavior. Existing World deployments need the paired integration before using this API.

Applying these fixes to existing scripts

Native TypeScript: request, then animate

For an object with an animation asset named dance in its contents, this native script requests permission from the toucher and starts that animation only after the granted mask permits it. Replace dance with the name of your animation asset.

import { WorldScript, ScriptPermission, type TouchAgent } from "poqpoq/types";

export default class PermissionDance extends WorldScript {
  onTouchStart(agent: TouchAgent) {
    this.requestPermissions(agent, ScriptPermission.TriggerAnimation);
  }

  onPermissions(mask: number) {
    if (mask & ScriptPermission.TriggerAnimation) {
      this.startAnimation("dance");
    }
  }
}

A request is not a grant. getPermissions() and permissionsKey read the current validated mask and granting avatar. startAnimation(name) and stopAnimation(name) target that avatar; World still checks permission and target. A callback that awaits other work must recheck current permissions before deciding what to do, because its original mask may be stale.

These native helpers require the matching updated SDK/editor and worker. Older native helpers sent an incomplete animation command; updated type declarations alone do not fix that runtime. On a deployment that has not received the worker update, wait for the runtime release before using this example. Check with your World host if you are unsure which runtime version is deployed.

The September 11–12 updates span both the compiler and World runtime. Runtime fixes such as wildcard listener matching, event serialization and permission-mask tracking and request correlation need an updated World client, not retranspilation. Generated-code fixes such as animation target selection also need the original LSL to be transpiled again with the updated compiler, then saved/reloaded through your normal script workflow.

Keep the original LSL. If an older imported script still fails after a client refresh, check which compiler generated its stored code. Loading this documentation does not update the editor, saved script, or World runtime. Resetting old generated code alone does not recompile it.

See also

Touch face and coordinates

Hosts can also provide optional position, normal, and binormal vectors in the touch surface record. Native TypeScript receives engine-frame (Y-up) geometry. LSL reads it through llDetectedTouchPos, llDetectedTouchNormal, and llDetectedTouchBinormal in Z-up coordinates. Missing geometry returns TOUCH_INVALID_VECTOR (0, 0, 0); ST and UV remain surface coordinates and are not converted between world axes.

Geometry requires support from your host's picking implementation. The SDK accepts these fields; World integration and live verification are pending. See the binormal and invalid-vector definitions.

Hosts may send an optional fourth touch argument, surface, with separate st: { x, y } and uv: { x, y } values. ST describes the surface; UV includes texture transforms. These are 2D coordinates and are not converted between world coordinate frames.

Compiled LSL reads these through llDetectedTouchST(0) and llDetectedTouchUV(0), as vectors with z = 0. Missing coordinates return TOUCH_INVALID_TEXCOORD (<-1, -1, 0>). llDetectedTouchFace(0) uses the supplied face. This applies to start, held and end events. Existing three-argument native handlers remain compatible.

This SDK support requires host data. World HUD coordinate plumbing is still being qualified; it is not available merely by upgrading the SDK. See the UV and ST references.