What is new in the release 2026-07-24

There is a new SLua release in the SLua regions on the Main Grid!

I’ve gathered all the info I could find about the changes coming in this release.

Scripts don’t need changes or to be recompiled to work with this release.

(this page updated on Tuesday, July 28th)

LLTimers

The behavior of LLTimers has changed: it no longer attempts to catch up on missed ticks when the delay is under 2 seconds.

To prevent timing drift, LLTimers normally tries to preserve the scheduled cadence (calculated as previous schedule + interval). However, a timer event can be delayed if the script is executing other events, or if the script is paused and resumed (e.g., during teleports, region crossings, attachments/detachments, rez/derez events, or region restarts).

When a delay occurs:

  • If the next scheduled run time is in the future (relative to the current time), the original cadence is preserved.
  • If the next scheduled run time is already in the past, the timer does not fire repeatedly in quick succession to catch up. Instead, it resets its cadence relative to the current time (now + interval).

Next timer in the future, cadence preserved:

-- pause (a teleport)
  
LLTimers:every(5, function(expected, interval)
    print(string.format("%18.15f%5.1f%20.15f", expected, interval, ll.GetTime()))
end)
--[[
    expected time     int       current time
   5.667029000003822  5.0   5.689430999977048
  10.667029000003822  5.0  10.689374000008684
  15.667029000003822  5.0  15.689446999982465
( a teleport here )
  20.667029000003822  5.0  22.911421999975573
( next timer in the future, cadence preserved)
  25.667029000003822  5.0  25.689406000019517
  30.667029000003822  5.0  30.689406000019517
]]

Next timer in the past, cadence reset, no catch-up:

-- pause (a teleport)
  
LLTimers:every(1, function(expected, interval)
    print(string.format("%18.15f%5.1f%20.15f", expected, interval, ll.GetTime()))
end)
--[[
    expected time     int       current time
   2.911445999983698  1.0   2.933622999989893
   3.911445999983698  1.0   3.933587000006810
   4.911445999983698  1.0   4.933663000003435
( a teleport here )
   5.911445999983698  1.0   8.111338999995496
( next timer in the past, cadence reset)
   9.111338999995496  1.0   9.111368999991100
  10.111338999995496  1.0  10.133544999989681
]]

Metamethod __call

The __call metamethod is never used as an iterator.

While Luau maintains this behavior to preserve backwards compatibility with legacy custom iterators, it is not necessary in SLua.

Previously, to use generalized iteration on a table that implemented a __call metamethod, we had to explicitly define an __iter metamethod (such as __iter = pairs)

llprim library

The new llprim library is designed to bridge the gap between LSL’s flat, list-based parameter structures and Lua’s advamced data structures.

The llprim library introduces structured alternatives, such as a fluent builder interface and dictionary tables, which make script configurations cleaner, more readable, and easier to manipulate dynamically.

Currently it has:

  • llprim.ParamsSetter : A builder class that wraps ll.SetLinkPrimitiveParamsFast() (as well as SetPrimitiveParams() and SetLinkPrimitiveParams()), enabling method chaining.
  • llprim.setMedia() : A dictionary-based wrapper for ll.SetLinkMedia() and ll.SetPrimMediaParams()
  • llprim.setParticleSystem() : A dictionary-based wrapper for ll.LinkParticleSystem() and ll.ParticleSystem()

The llprim library doesn’t add new functionality but allows us to work in a more Lua way.

llprim.ParamsSetter

Wrapper of ll.SetLinkPrimitiveParamsFast() (and also SetPrimitiveParams() and SetLinkPrimitiveParams())

llprim.ParamsSetter is a builder class that simplifies the construction of primitive parameter lists. Instead of manually assembling a flat table of PRIM_ constants, we instantiate a helper object and chain descriptive methods together.

Each method call appends its corresponding rule to an internal table and returns the helper object itself (self), enabling a fluent interface. The accumulated changes are deferred and then sent to the simulator atomically in a single native call when we invoke the :apply() method.

Basic Usage and Immediate Application

For simple modifications, we can instantiate the builder, chain the desired property changes, and apply them immediately. Each method has the same parameters as the constants:

-- Simple use, apply params immediately
llprim.ParamsSetter.new()
  :targetLink(LINK_ROOT)
  :color(ALL_SIDES, vector(0,0,1), 1)
  :text("first", vector.one, 1)
  :apply()

Deferred and Batch Construction

Because the builder accumulates rules in an underlying table before execution, we do not have to apply changes immediately. We can define a builder, populate it dynamically inside loops or conditional blocks, and trigger the update atomically when ready. This is highly efficient, as it reduces the overhead of making multiple, separate LSL-style parameter calls:

-- But we can also collect rules to build upon and apply later
-- Here we arrange the child links in a line above the root
local rules = llprim.ParamsSetter.new()

for i=2,ll.GetNumberOfPrims() do
  -- The method calls really just append an SPP rule then return **self**
  rules
    :targetLink(i)
    :pos(ll.GetPos() + vector(0,0,1 * i))
end

-- And we apply the changes atomically as one `ll.SetLinkPrimitiveParamsFast()` under the hood.
rules:apply()

Manipulating the Rule Set

Under the hood, a ParamsSetter instance is a standard Lua table array. This allows us to use library functions like table.extend() to merge rules from other sources, or to append raw LSL constants manually:

-- We can use `table.extend()` to mash rules onto it from other lists or builders
table.extend(rules, {PRIM_PHYSICS, false})

Cloning Configurations

Since the builder is a table, we can duplicate an existing set of rules using table.clone(). This allows us to define a base template configuration and modify it for different target links or sides without rebuilding the entire parameter set from scratch:

-- Cloning is fine too
local rules2 = table.clone(rules)
rules2
  :targetLink(LINK_THIS)
  :text("We can tack rules onto the clone", vector.one, 1.0)
  :apply()

Coercing Tables into Builders If we have an existing raw parameter list, we do not need to rewrite it to use the fluent interface. By setting its metatable to llprim.ParamsSetter, we upgrade the table into a builder, gaining access to all of its chaining methods and the :apply() function.

-- The source table can come from anywhere, we can just give it the metatable to turn it into a builder.
local someOtherTable: {any} = {PRIM_POSITION, ll.GetPos() + vector.one}

setmetatable(someOtherTable, llprim.ParamsSetter)

someOtherTable
  :text("And now it's a ParamsSetter", vector.one, 1)
  :apply()

Method Name Mapping

The methods available on ParamsSetter are derived from the standard PRIM_ constants, with the prefix removed and in camelCase. Several have been shortened or updated to be more intuitive. Unused or deprecated constants have been excluded:

Constant Name Method Name Change
PRIM_NAME name
PRIM_DESC description Changed
PRIM_TYPE type
PRIM_SLICE slice
PRIM_PHYSICS_SHAPE_TYPE physicsShapeType
PRIM_MATERIAL physicsMaterial Changed
PRIM_PHYSICS physical Changed
PRIM_TEMP_ON_REZ temporary Changed
PRIM_PHANTOM phantom
PRIM_POSITION pos Changed
PRIM_POS_LOCAL posLocal
PRIM_ROTATION rot Changed
PRIM_ROT_LOCAL rotLocal
PRIM_SIZE size
PRIM_TEXTURE texture
PRIM_RENDER_MATERIAL renderMaterial
PRIM_TEXT text
PRIM_COLOR color
PRIM_BUMP_SHINY shinyBump Changed
PRIM_POINT_LIGHT pointLight
PRIM_REFLECTION_PROBE reflectionProbe
PRIM_FULLBRIGHT fullbright
PRIM_FLEXIBLE flexible
PRIM_TEXGEN texgen
PRIM_GLOW glow
PRIM_OMEGA omega
PRIM_NORMAL normal
PRIM_SPECULAR specular
PRIM_ALPHA_MODE alphaMode
PRIM_LINK_TARGET targetLink Changed
PRIM_CAST_SHADOWS - Deprecated
PRIM_LEGACY - Deprecated
PRIM_ALLOW_UNSIT allowUnsit
PRIM_SCRIPTED_SIT_ONLY scriptedSitOnly
PRIM_SIT_TARGET sitTarget
PRIM_PROJECTOR projector
PRIM_CLICK_ACTION clickAction
PRIM_GLTF_BASE_COLOR gltfBaseColor
PRIM_GLTF_NORMAL gltfNormal
PRIM_GLTF_METALLIC_ROUGHNESS gltfMetallicRoughness
PRIM_GLTF_EMISSIVE gltfEmissive
PRIM_SIT_FLAGS sitFlags
PRIM_DAMAGE damage
PRIM_HEALTH health
PRIM_COLLISION_SOUND collisionSound

llprim.setMedia()

Wrapper of ll.SetLinkMedia() and ll.SetPrimMediaParams()

The llprim.setMedia() function wraps the configuration of face media (Shared Media). Instead of passing a flat array of alternating PRIM_MEDIA_ constants and values, this wrapper allows us to define the configuration using a structured Lua dictionary (a table with key-value pairs).

The function accepts three parameters:

  • The target face number.
  • A configuration dictionary table containing the media settings.
  • An optional link target, which defaults to LINK_THIS if omitted.

This wrapper handles the necessary translation from dictionary keys to LSL media constants under the hood.

Be aware that any unrecognized keys in the dictionary are silently ignored during execution. Because the runtime does not throw a script error for invalid keys, typographical errors can be difficult to detect. Always double-check your spelling against the reference table:

local link = nil

llprim.setMedia(0, {
    current_url          = "https://example.com/",
    home_url             = "https://example.com/",
    auto_play            = true,
    auto_loop            = false,
    controls             = PRIM_MEDIA_CONTROLS_STANDARD,
    width_pixels         = 1024,
    height_pixels        = 768,
    whitelist_enable     = true,
    whitelist            = { "example.com", "*.example.com" },
    perms_interact       = PRIM_MEDIA_PERM_ANYONE,
    perms_control        = PRIM_MEDIA_PERM_OWNER,
}, link)

-- Note, the first parameter is the face.  The last parameter is the link number.  If link number is omitted 
-- it defaults to LINK_THIS

Key Name Mapping

The configuration keys map directly to the native PRIM_MEDIA_ constants, with the prefix removed and in lowercase. Several have been shortened or updated to be more intuitive:

Constant Name Key Name Change
PRIM_MEDIA_ALT_IMAGE_ENABLE alt_image_enable
PRIM_MEDIA_CONTROLS controls
PRIM_MEDIA_CURRENT_URL current_url
PRIM_MEDIA_HOME_URL home_url
PRIM_MEDIA_AUTO_LOOP auto_loop
PRIM_MEDIA_AUTO_PLAY auto_play
PRIM_MEDIA_AUTO_SCALE auto_scale
PRIM_MEDIA_AUTO_ZOOM auto_zoom
PRIM_MEDIA_FIRST_CLICK_INTERACT first_click_interact
PRIM_MEDIA_WIDTH_PIXELS width Changed
PRIM_MEDIA_HEIGHT_PIXELS height Changed
PRIM_MEDIA_WHITELIST_ENABLE whitelist_enable
PRIM_MEDIA_WHITELIST whitelist
PRIM_MEDIA_PERMS_INTERACT perms_interact
PRIM_MEDIA_PERMS_CONTROL perms_control

llprim.setParticleSystem()

Wrapper of ll.LinkParticleSystem() and ll.ParticleSystem()

The llprim.setParticleSystem function simplifies the creation and modification of particle effects. Instead of building the complex, flat list structure required by native particle functions, we pass a single dictionary of properties.

The function takes two parameters:

  • A configuration dictionary table containing the particle rules.
  • An optional link target, which defaults to LINK_THIS if omitted.

This wrapper handles the necessary translation from dictionary keys to LSL particle system constants under the hood.

Be aware that any unrecognized keys in the dictionary are silently ignored during execution. Because the runtime does not throw a script error for invalid keys, typographical errors can be difficult to detect. Always double-check your spelling against the reference table:

llprim.setParticleSystem({
    pattern       = PSYS_SRC_PATTERN_ANGLE_CONE,
    texture       = "da8e96f5-4ada-4f37-bd2c-cf3e68c49a42",
    color_begin   = vector(1, 0.6, 0.1),
    color_end     = vector(1, 0.1, 0),
    alpha_begin   = 1.0,
    alpha_end     = 0.0,
    scale_begin   = vector(0.1, 0.1, 0),
    scale_end     = vector(0.4, 0.4, 0),
    glow_begin    = 0.5,
    glow_end      = 0.0,
    accel         = vector(0, 0, 0.5),
    burst_speed_min = 0.5,
    burst_speed_max = 1.5,
    burst_rate    = 0.05,
    burst_count   = 5,
    burst_radius  = 0.1,
    src_max_age   = 0,     -- 0 = continuous
    part_max_age  = 3.0,
    color_interp  = true,
    scale_interp  = true,
    emissive      = true,
    wind          = false,
}, link)

-- note link is an optional parameter.  If missing it defaults to LINK_THIS

Key Name Mapping

The configuration keys map directly to the native PSYS_ constants, with the prefix removed and in lowercase. Several have been shortened or updated to be more intuitive:

Constant Name Key Name Change
PSYS_PART_FLAGS part_flags
PSYS_SRC_PATTERN src_pattern
PSYS_SRC_BURST_RADIUS src_burst_radius
PSYS_SRC_ANGLE_BEGIN src_angle_begin
PSYS_SRC_ANGLE_END src_angle_end
PSYS_SRC_INNERANGLE angle_inner Changed
PSYS_SRC_OUTERANGLE angle_outer Changed
PSYS_SRC_TARGET_KEY src_target_key
PSYS_PART_START_COLOR color_begin Changed
PSYS_PART_END_COLOR color_end Changed
PSYS_PART_START_ALPHA alpha_begin Changed
PSYS_PART_END_ALPHA alpha_end Changed
PSYS_PART_START_SCALE scale_begin Changed
PSYS_PART_END_SCALE scale_end Changed
PSYS_SRC_TEXTURE src_texture
PSYS_PART_START_GLOW glow_begin Changed
PSYS_PART_END_GLOW glow_end Changed
PSYS_PART_BLEND_FUNC_SOURCE part_blend_func_source
PSYS_PART_BLEND_FUNC_DEST part_blend_func_dest
PSYS_SRC_MAX_AGE src_max_age
PSYS_PART_MAX_AGE part_max_age
PSYS_SRC_BURST_RATE src_burst_rate
PSYS_SRC_BURST_PART_COUNT src_burst_part_count
PSYS_SRC_ACCEL src_accel
PSYS_SRC_OMEGA src_omega
PSYS_SRC_BURST_SPEED_MIN src_burst_speed_min
PSYS_SRC_BURST_SPEED_MAX src_burst_speed_max

Parameters as dictionaries

To complement the helper functions in the llprim library, several native LL functions have been enhanced to natively accept dictionaries as parameters. This allows us to write clean, structured key-value tables directly inside standard LSL function calls, eliminating the need to construct or flatten arrays.

ll.SetPrimMediaParams() and ll.SetLinkMedia()

The media functions ll.SetPrimMediaParams() and ll.SetLinkMedia() accept a dictionary as their configuration parameter. This dictionary uses the exact same keys as the llprim.setMedia() wrapper:

ll.SetLinkMedia(0, 0, {
    current_url          = "https://example.com/",
    home_url             = "https://example.com/",
    auto_play            = true,
    auto_loop            = false,
    controls             = PRIM_MEDIA_CONTROLS_STANDARD,
    width                = 1024,
    height               = 768,
    whitelist_enable     = true,
    whitelist            = { "example.com", "*.example.com" },
    perms_interact       = PRIM_MEDIA_PERM_ANYONE,
    perms_control        = PRIM_MEDIA_PERM_OWNER,
})

The keys used in the dictionary table are the same as llprim.setMedia().

ll.ParticleSystem() and ll.LinkParticleSystem()

ll.ParticleSystem() and ll.LinkParticleSystem() accept a structured dictionary instead of a flat list. The keys and rules are identical to those defined for llprim.setParticleSystem():

ll.LinkParticleSystem(0, {
    pattern       = PSYS_SRC_PATTERN_ANGLE_CONE,
    texture       = "da8e96f5-4ada-4f37-bd2c-cf3e68c49a42",
    color_begin   = vector(1, 0.6, 0.1),
    color_end     = vector(1, 0.1, 0),
    alpha_begin   = 1.0,
    alpha_end     = 0.0,
    scale_begin   = vector(0.1, 0.1, 0),
    scale_end     = vector(0.4, 0.4, 0),
    glow_begin    = 0.5,
    glow_end      = 0.0,
    accel         = vector(0, 0, 0.5),
    burst_speed_min = 0.5,
    burst_speed_max = 1.5,
    burst_rate    = 0.05,
    burst_count   = 5,
    burst_radius  = 0.1,
    src_max_age   = 0,     -- 0 = continuous
    part_max_age  = 3.0,
    color_interp  = true,
    scale_interp  = true,
    emissive      = true,
    wind          = false,
})

The keys used in the dictionary table are the same as llprim.setParticleSystem().

ll.HTTPRequest()

Constructing metadata, headers, and options for HTTP requests in LSL can be cumbersome due to the alternating parameter list format. In SLua, ll.HTTPRequest() accepts a structured dictionary for its options parameter.

This dictionary allows us to configure HTTP methods, custom headers, content types, and validation settings in an organized, readable block.

CSV strings and key-value map strings can be written as Lua arrays and dictionaries.

Be aware that any unrecognized keys in the dictionary are silently ignored during execution. Because the runtime does not throw a script error for invalid keys, typographical errors can be difficult to detect. Always double-check your spelling against the reference table:

local request_id =ll.HTTPRequest(
    "https://api.example.com/data",
    {
        method          = "POST",
        mimetype        = "application/json",
        verify_cert     = true,
        extended_error  = true,
        custom_header   = {
            ["X-Api-Key"]        = "my-secret-key",
            ["X-Request-Source"] = "second-life",
        },
        accept          = { "application/json", "text/plain" },
    },
    '{"hello": "world"}'
)

print(request_id)

There is no new library function to wrap ll.HTTPRequest().

Key Name Mapping

The configuration keys map directly to the native HTTP_ constants, with the prefix removed and in lowercase. Several have been shortened or updated to be more intuitive:

Constant Name Key Name Change
HTTP_METHOD method
HTTP_MIMETYPE mimetype
HTTP_BODY_MAXLENGTH max_body_length Changed
HTTP_VERIFY_CERT verify_cert
HTTP_VERBOSE_THROTTLE verbose_throttle
HTTP_CUSTOM_HEADER custom_header
HTTP_PRAGMA_NO_CACHE pragma_no_cache
HTTP_USER_AGENT user_agent
HTTP_ACCEPT accept
HTTP_EXTENDED_ERROR extended_error