Run State Breaks Glow and Transparency on moving linkset.
Neon Zombie
Depending on the running state of the script the glow and transparency appears to break.
The provided script only handles the scale and movement of the object, it doesn't set any texture or material values which makes me think its movement related.
- Create a linkset of 65 cubes.
- Set them all to the default white texture in the blinn-phong texture window.
- Set the glow value to 1 and the transparent value to any non-zero value.
- Create a new script and insert the following SLua script.
Watch the state of the glow and transparency when toggling the scripts running state.
-- Neon Zombie 2026.
-- SLua linkset movement testing
-- 65 links total: 1 root, 64 children. Child cube default scale dynamically set
-- Linkset
local FIRST_CHILD_LINK = 2
local LAST_CHILD_LINK = 65
local TOTAL_CHILDREN = LAST_CHILD_LINK - FIRST_CHILD_LINK + 1
local GRID_SIZE = 4
local CUBE_SIZE = 0.025
local MIN_SEPARATION = CUBE_SIZE * 2.0
local volume_area = 0.06
local TIMER_RATE = 0.02
local SPRING = 16.0
local DAMPING = 0.90
local MASS = 1.0
local MAX_DT = 0.033
-- Collision
local COLLISION_DIST = CUBE_SIZE * 1.75
local COLLISION_DIST_SQ = COLLISION_DIST * COLLISION_DIST
local REPULSION_FORCE = 2000.0
-- Localized Math
local m_sin = math.sin
local m_cos = math.cos
local m_sqrt = math.sqrt
local m_pi = math.pi
local m_random = math.random
local function v(x, y, z) return vector(x, y, z) end
local ZERO_VECTOR = v(0, 0, 0)
local ZERO_ROTATION = ll.Euler2Rot(ZERO_VECTOR)
local state = {}
local primCache = {}
local timerHandle = nil
local startTime = 0
local lastTime = 0
local cubeSizeVec = v(CUBE_SIZE, CUBE_SIZE, CUBE_SIZE)
-- Precompute float paths and stack positions, its much much faster.
local function buildCaches()
local gridHalf = ((GRID_SIZE - 1) * MIN_SEPARATION) * 0.5
for i = 0, TOTAL_CHILDREN - 1 do
local x = i % GRID_SIZE
local y = math.floor(i / GRID_SIZE) % GRID_SIZE
local z = math.floor(i / (GRID_SIZE * GRID_SIZE))
local stackPos = v(
(x * MIN_SEPARATION) - gridHalf,
(y * MIN_SEPARATION) - gridHalf,
(z * MIN_SEPARATION) - gridHalf
)
primCache[i] = {
stackTarget = stackPos,
-- Expanded movement area amplitudes
ax = 0.04 + m_random() * volume_area,
ay = 0.04 + m_random() * volume_area,
az = 0.02 + m_random() * volume_area,
sx = 0.8 + m_random() * 0.4,
sy = 0.8 + m_random() * 0.4,
sz = 0.6 + m_random() * 0.4,
phaseX = m_random() * 2 * m_pi,
phaseY = m_random() * 2 * m_pi,
phaseZ = m_random() * 2 * m_pi
}
state[i] = { pos = stackPos, vel = ZERO_VECTOR }
end
end
-- Generates the destination coordinate for a specific cube at time 't'
local function getFloatTarget(i, t)
local c = primCache[i]
return c.stackTarget + v(
m_sin(t * c.sx + c.phaseX) * c.ax,
m_cos(t * c.sy + c.phaseY) * c.ay,
m_sin(t * c.sz + c.phaseZ) * c.az
)
end
local function updatePhysics(t, dt, snap)
if dt > MAX_DT then dt = MAX_DT end
local batchParams = {}
local pIdx = 1
-- Immediate snap resolution
if snap then
for i = 0, TOTAL_CHILDREN - 1 do
local link = i + FIRST_CHILD_LINK
local target = primCache[i].stackTarget
state[i].pos = target
state[i].vel = ZERO_VECTOR
batchParams[pIdx] = PRIM_LINK_TARGET
batchParams[pIdx+1] = link
batchParams[pIdx+2] = PRIM_SIZE
batchParams[pIdx+3] = cubeSizeVec
batchParams[pIdx+4] = PRIM_POS_LOCAL
batchParams[pIdx+5] = target
batchParams[pIdx+6] = PRIM_ROT_LOCAL
batchParams[pIdx+7] = ZERO_ROTATION
pIdx = pIdx + 8
end
ll.SetLinkPrimitiveParamsFast(0, batchParams)
return
end
-- Calculate Base Forces
local currentForces = {}
for i = 0, TOTAL_CHILDREN - 1 do
currentForces[i] = (getFloatTarget(i, t) - state[i].pos) * SPRING
end
-- Collision Pass
for i = 0, TOTAL_CHILDREN - 1 do
local pi = state[i].pos
for j = i + 1, TOTAL_CHILDREN - 1 do
local pj = state[j].pos
local dx = pi.x - pj.x
local dy = pi.y - pj.y
local dz = pi.z - pj.z
local distSq = dx*dx + dy*dy + dz*dz
-- If inside collision radius (ignoring perfectly overlapping zero-distance vectors)
if distSq < COLLISION_DIST_SQ and distSq > 0.000001 then
local dist = m_sqrt(distSq)
local overlap = COLLISION_DIST - dist
-- Calculate normalized repulsion vector scaled by overlap severity
local pushMag = overlap * REPULSION_FORCE
local push = v((dx/dist)*pushMag, (dy/dist)*pushMag, (dz/dist)*pushMag)
-- Apply equal and opposite penalty forces
currentForces[i] = currentForces[i] + push
currentForces[j] = currentForces[j] - push
end
end
end
-- Batch Update
for i = 0, TOTAL_CHILDREN - 1 do
local link = i + FIRST_CHILD_LINK
local s = state[i]
-- Apply force to velocity, dampen, and update position
local accel = currentForces[i] / MASS
s.vel = (s.vel + accel * dt) * DAMPING
s.pos = s.pos + (s.vel * dt)
-- Dynamic rotation: makes the cubes tumble as they move
local rot = ll.Euler2Rot(v(s.vel.y * 2.0, s.vel.x * 2.0, 0))
batchParams[pIdx] = PRIM_LINK_TARGET
batchParams[pIdx+1] = link
batchParams[pIdx+2] = PRIM_POS_LOCAL
batchParams[pIdx+3] = s.pos
batchParams[pIdx+4] = PRIM_ROT_LOCAL
batchParams[pIdx+5] = rot
pIdx = pIdx + 6
end
if #batchParams > 0 then
ll.SetLinkPrimitiveParamsFast(0, batchParams)
end
end
local function onTimer()
local now = os.clock()
local dt = now - lastTime
lastTime = now
local t = now - startTime
-- Update physics (snap = false)
updatePhysics(t, dt, false)
end
-- Init
local function startup()
math.randomseed(math.floor(os.clock() * 100000))
buildCaches()
-- Snap all cubes to stack immediately
updatePhysics(0, 0, true)
startTime = os.clock()
lastTime = startTime
if timerHandle then
LLTimers:off(timerHandle)
end
timerHandle = LLTimers:every(TIMER_RATE, function()
local success, err = pcall(onTimer)
if not success then
ll.OwnerSay("Runtime Error in Timer: " .. tostring(err))
LLTimers:off(timerHandle)
timerHandle = nil
end
end)
ll.OwnerSay("Asteroid Simulation Running. Touch to reset and scatter.")
end
-- Resets the layout.
LLEvents:on("touch_start", function(detected)
-- Randomize the float targets for a new pattern on every click
buildCaches()
-- Snap them all to the tight grid and let physics push them apart on the next frame
updatePhysics(os.clock() - startTime, 0, true)
end)
startup()
Log In
Maestro Linden
I can reproduce this issue. My environment is in a child comment.
I tested with a linkset of 65 default boxes, each with a blank white texture, glow=1.0, and 1% transparency. When I run the script, it rescales each of the 64 child prims to a 0.025m cube and causes them to 'bounce around' inside the walls of the 0.5m root prim. While this is happening, the glow on most (but not all) child prims stops rendering. This 'missing glow' continues until I set the script to stop running, at which points _most_ of the child prims regain their glow effect.
Here's a video illustrating the issue:
This appears to be a viewer rendering issue, in which the viewer is (possibly intentionally) closing not to render the glow shader. Details below.
Maestro Linden
Looking at the incoming message stream with Hippolyzer, there's an initial burst of
ObjectUpdate
messages showing the resize of the prims, then (as expected) an absolute storm of ImprovedTerseObjectUpdate
messages corresponding to the child prims moving around continuously.Here's a snippet of one of the
ObjectUpdate
messages (the full message is far too big for canny). We see that the scale is resized to 0.025m and that glow is set to 1.0 on all faces:...
Scale = <0.02500000037252903, 0.02500000037252903, 0.02500000037252903>
ObjectData =| (60, \
{'Position': (0.02500000037252903, -0.02500000037252903, -0.02500000037252903), \
'Velocity': (0.0, 0.0, 0.0), \
'Acceleration': (0.0, 0.0, 0.0), \
'Rotation': (0.0, 0.0, 0.0), \
'AngularVelocity': (0.0, 0.0, 0.0)})
#ObjectData = b'\xcd\xcc\xcc<\xcd\xcc\xcc\xbc\xcd\xcc\xcc\xbc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
ParentID = 4110953
TextureEntry =| {'Textures': {None: '5748decc-f629-461c-9a36-a35a221fe21f', \
(0, 1, 2, 3, 4): '5748decc-f629-461c-9a36-a35a221fe21f'}, \
'Color': {None: b'\xff\xff\xff\xfc', (0, 1, 2, 3, 4): b'\xff\xff\xff\xfc'}, \
'ScalesS': {None: 1.0, (0, 1, 2, 3, 4): 1.0}, \
'ScalesT': {None: 1.0, (0, 1, 2, 3, 4): 1.0}, \
'OffsetsS': {None: 0.0, (0, 1, 2, 3, 4): 0.0}, \
'OffsetsT': {None: 0.0, (0, 1, 2, 3, 4): 0.0}, \
'Rotation': {None: 0.0, (0, 1, 2, 3, 4): 0.0}, \
'BasicMaterials': {None: {'Bump': 0, 'FullBright': False, 'Shiny': 'OFF'}, \
(0, 1, 2, 3, 4): {'Bump': 0, 'FullBright': False, 'Shiny': 'OFF'}}, \
'MediaFlags': {None: {'WebPage': False, 'TexGen': 'DEFAULT', '_Unused': 0}, \
(0, 1, 2, 3, 4): {'WebPage': False, 'TexGen': 'DEFAULT', '_Unused': 0}}, \
'Glow': {None: 1.0, (0, 1, 2, 3, 4): 1.0}, \
...
The
ImprovedTerseObjectUpdate
messages come after that don't describe parameters like glow at all, but instead reports on the position/velocity/etc. changes of each prim as it animates. Anyway, the viewer knows that the prims have glow enabled, but choses not to render glow while the constant updates continue. This may be a render performance optimization in play.Maestro Linden
environment:
Second Life Project Lua Editor 26.3.0.30154945611 (64bit)
Release Notes
You are at 128.0, 128.0, 3000.1 in Undisclosed located at simhost-08ead0889a544632b.aditi
SLURL: secondlife://Aditi/secondlife/Undisclosed/128/128/3000
(global coordinates 41600.0, 22144.0, 3000.1)
Luau 2026-07-24.30110090421
Release Notes
CPU: Apple M1 Pro (2400 MHz)
Memory: 16384 MB
OS Version: macOS 26.5.2 Darwin 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun 9 22:18:58 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T6000 arm64
Graphics Card Vendor: Apple
Graphics Card: Apple M1 Pro
OpenGL Version: 4.1 Metal - 90.5
Window size: 1227x695
Font Size Adjustment: 96pt
UI Scaling: 0.75
Draw distance: 64m
Bandwidth: 10000kbit/s
LOD factor: 1.375
Render quality: 3
Texture memory: 12124MB
Disk cache: Max size 2150.4 MB (100.0% used)
HiDPI display mode:
J2C Decoder Version: KDU v8.4.1
Audio Driver Version: OpenAL, version 1.1 ALSOFT 1.24.2 / OpenAL Community / OpenAL Soft: OpenAL Soft
Dullahan: 1.26.0.202510161627
CEF: 139.0.40+g465474a+chromium-139.0.7258.139
Chromium: 139.0.7258.139
LibVLC Version: 3.0.21
Voice Server Version: Not Connected
Packets Lost: 0/5674 (0.0%)
August 04 2026 11:54:09
Frio Belmonte
Maestro Linden Trying to track down a "glow is rendered in the wrong position" problem with an effect of mine I tested this since it seems related: after stopping the effect, moving the camera around will sometimes render a batch of glow in a completely wrong position. Unfortunately can't offer an exact reproduction condition, it comes and goes as it pleases. For my object it's similarly flaky, depending on camera position and what kind of mood the viewer happens to be in.
If I toggle scripts to running via Firestorm's "object->set scripts to running" while the root prim is selected, the child prims will not interpolate their movement and will render glow mostly correctly (same caveat as above: not all glow is rendered after stopping the script); after deselecting the object most of the glow goes missing. Another prim viewer interpolation issue at its heart? That would explain why my object is acting up, it does not
move
prims but instead uses TargetOmega. In my case there are only two prims (I think it's one mesh with a glowing face, one sphere prim) doing the glow rendering along with some particles, so there certainly aren't as many separate objects involved.Tested with FS 7.2.4 and Project Lua viewer 26.1.0.23768336784 on Windows.