// Pumpkin Spider: a jack-o'-lantern on eight legs that scuttles after its owner. Feet are planted in the world
// (ray down, terrain fallback); two-bone IK with knees up and out, like a spider; alternating tetrapod gait (four
// legs swing while four stand). The face flickers like a candle. Touch: follow / stay. Owner /7: come, stay,
// pounce, reset. For a friend, 2026-10-11, grown from Burger's legs (shibawalk.lsl). Claude Opus 5.5 (Tessel).
float L1 = 0.32; // thigh
float L2 = 0.38; // shin
float H = 0.42; // body centre above the ground
float SPEED = 0.9;
float RUN = 2.6;
float TURN = 2.0;
float STEP = 0.12;
float SWING = 0.2;
integer PRIMS;
float DT = 0.08;
float NEAR = 1.8;
list LEGS = ["l1", "l2", "l3", "l4", "r1", "r2", "r3", "r4"];
list ANG = [35, 75, 110, 145, -35, -75, -110, -145]; // hip angles around the body, degrees from forward
float HIP_R = 0.2; float HIP_Z = -0.02; float HOME_R = 0.62;
list hips; list homes; list poles;
list thighL; list shinL; list footL;
list planted; list swingFrom; list swingTo;
integer swingGroup = -1;
float swingT0;
integer FACE;
vector pos; float yaw; float pitch; key target = NULL_KEY;
float hopT0; float baseZ; vector hopFrom; vector hopTo; integer hopped; integer landed;
float hNow;
integer link_named(string n)
{
integer i = llGetNumberOfPrims();
for (; i > 1; --i) if (llGetLinkName(i) == n) return i;
return 0;
}
float zof(vector v) { return v.z; }
// tetrapod: l1, r2, l3, r4 together; r1, l2, r3, l4 together
integer group_of(integer leg) { if (leg < 4) return leg % 2; return (leg - 3) % 2; }
float ground(vector p, float fallback)
{
list r = llCastRay(
, ,
[RC_REJECT_TYPES, RC_REJECT_AGENTS | RC_REJECT_PHYSICAL, RC_DATA_FLAGS, RC_GET_ROOT_KEY, RC_MAX_HITS, 4]);
integer n = llList2Integer(r, -1);
integer i;
for (i = 0; i < n; ++i)
if (llList2Key(r, i * 2) != llGetKey()) return zof(llList2Vector(r, i * 2 + 1));
vector off = p - llGetPos();
float t = llGround();
if (t > pos.z - 2.0 && t < pos.z + 0.5) return t;
return fallback;
}
vector home_world(integer leg, rotation rot)
{
vector h = llList2Vector(homes, leg) * rot + pos;
return ;
}
list leg_params(integer leg, vector footW, rotation rot)
{
vector hip = llList2Vector(hips, leg);
vector d = (footW - pos) / rot - hip;
float dist = llVecMag(d);
vector dir = d / dist;
if (dist > L1 + L2 - 0.01) dist = L1 + L2 - 0.01;
if (dist < 0.1) dist = 0.1;
float c = (L1 * L1 + dist * dist - L2 * L2) / (2 * L1 * dist);
if (c > 1) c = 1; if (c < -1) c = -1;
float a = llAcos(c);
vector pole = llList2Vector(poles, leg); // knees up and out
vector p = pole - dir * (pole * dir);
if (llVecMag(p) < 0.01) p = <0, 0, 1>;
p = llVecNorm(p);
vector knee = hip + dir * (L1 * llCos(a)) + p * (L1 * llSin(a));
vector end = hip + dir * dist;
vector up = <0, 0, 1>;
return [PRIM_LINK_TARGET, llList2Integer(thighL, leg), PRIM_POS_LOCAL, (hip + knee) / 2, PRIM_ROT_LOCAL, llRotBetween(up, llVecNorm(knee - hip)),
PRIM_LINK_TARGET, llList2Integer(shinL, leg), PRIM_POS_LOCAL, (knee + end) / 2, PRIM_ROT_LOCAL, llRotBetween(up, llVecNorm(end - knee)),
PRIM_LINK_TARGET, llList2Integer(footL, leg), PRIM_POS_LOCAL, end + <0, 0, 0.012>, PRIM_ROT_LOCAL, ZERO_ROTATION];
}
setup()
{
hips = []; homes = []; poles = []; thighL = []; shinL = []; footL = [];
integer i;
for (i = 0; i < 8; ++i)
{
float a = llList2Float(ANG, i) * DEG_TO_RAD;
vector out = ;
hips += <0.0, 0.0, HIP_Z> + out * HIP_R;
homes += out * HOME_R + <0.04, 0, 0>;
poles += llVecNorm(out * 0.4 + <0, 0, 1>);
string l = llList2String(LEGS, i);
thighL += link_named(l + "_thigh"); shinL += link_named(l + "_shin"); footL += link_named(l + "_foot");
}
for (i = 2; i <= llGetObjectPrimCount(llGetKey()); ++i) llSetLinkPrimitiveParamsFast(i, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_NONE]);
llSetLinkPrimitiveParamsFast(LINK_ROOT, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]);
FACE = link_named("face");
hNow = H; hopT0 = 0; pitch = 0;
pos = llGetPos();
vector e = llRot2Euler(llGetRot()); yaw = e.z;
pos.z = ground(pos, pos.z - H) + H;
rotation rot = llEuler2Rot(<0, 0, yaw>);
planted = [];
for (i = 0; i < 8; ++i) planted += home_world(i, rot);
swingFrom = planted; swingTo = planted; swingGroup = -1;
target = NULL_KEY;
pose(rot);
}
pose(rotation rot)
{
list p = [PRIM_POSITION, pos, PRIM_ROTATION, rot];
integer i;
for (i = 0; i < 8; ++i)
{
vector f = llList2Vector(planted, i);
if (swingGroup >= 0 && group_of(i) == swingGroup)
{
float s = (llGetTime() - swingT0) / SWING;
if (s > 1) s = 1;
float e = s * s * (3 - 2 * s);
vector a = llList2Vector(swingFrom, i); vector b = llList2Vector(swingTo, i);
f = a + (b - a) * e + <0, 0, 0.12 * llSin(PI * s)>;
}
p += leg_params(i, f, rot);
}
llSetLinkPrimitiveParamsFast(LINK_ROOT, p);
}
pounce()
{
if (hopT0 > 0) return;
if (swingGroup >= 0) { planted = swingTo; swingGroup = -1; }
hopT0 = llGetTime(); baseZ = pos.z; hopped = FALSE; landed = FALSE;
llWhisper(0, "*skitter skitter* POUNCE!");
}
// crouch low, leap forward with legs tucked up, land with a dip
pounce_tick()
{
float t = llGetTime() - hopT0;
rotation rot = llEuler2Rot(<0, 0, yaw>);
vector fwd = ;
integer i;
if (t < 0.4) { float e = t / 0.4; e = e * e * (3 - 2 * e); pos.z = baseZ - 0.18 * e; }
else if (t < 0.95)
{
if (!hopped)
{
hopped = TRUE; hopFrom = pos;
vector to = pos + fwd * 1.4;
hopTo = ;
}
float s = (t - 0.4) / 0.55;
pos = hopFrom + (hopTo - hopFrom) * s + <0, 0, 0.7 * llSin(PI * s)>;
planted = [];
for (i = 0; i < 8; ++i) planted += pos + (llList2Vector(homes, i) * 0.7 + <0, 0, -0.25>) * rot;
}
else if (t < 1.3)
{
if (!landed)
{
landed = TRUE; pos = hopTo;
planted = [];
for (i = 0; i < 8; ++i) planted += home_world(i, rot);
float gz = 0;
for (i = 0; i < 8; ++i) gz += zof(llList2Vector(planted, i));
baseZ = gz / 8 + H;
}
pos.z = baseZ - 0.12 * llSin(PI * (t - 0.95) / 0.35);
}
else { hopT0 = 0; pos.z = baseZ; }
pose(rot);
}
default
{
state_entry() { PRIMS = llGetObjectPrimCount(llGetKey()); setup(); llListen(7, "", llGetOwner(), ""); llSetTimerEvent(DT); }
on_rez(integer n) { llResetScript(); }
changed(integer c) { if ((c & CHANGED_LINK) && llGetObjectPrimCount(llGetKey()) != PRIMS) llResetScript(); } // sitters are links too
touch_start(integer n)
{
key who = llDetectedKey(0);
if (target == who) { target = NULL_KEY; llWhisper(0, "*settles on eight legs, grinning*"); }
else { target = who; llWhisper(0, "*skitters after " + llGetDisplayName(who) + "*"); }
}
listen(integer ch, string name, key id, string msg)
{
msg = llToLower(llStringTrim(msg, STRING_TRIM));
if (msg == "come") target = id;
else if (msg == "stay") target = NULL_KEY;
else if (msg == "reset") llResetScript();
else if (msg == "pounce") pounce();
}
timer()
{
// candle flicker
if (FACE) llSetLinkPrimitiveParamsFast(FACE, [PRIM_GLOW, ALL_SIDES, 0.16 + llFrand(0.14)]);
if (hopT0 > 0) { pounce_tick(); return; }
vector fwd = ;
float v = 0;
if (target != NULL_KEY)
{
list od = llGetObjectDetails(target, [OBJECT_POS]);
if (od == []) target = NULL_KEY;
else
{
vector to = llList2Vector(od, 0) - pos; to.z = 0;
float dist = llVecMag(to);
if (dist > NEAR)
{
float want = llAtan2(to.y, to.x);
float err = want - yaw;
while (err > PI) err -= TWO_PI;
while (err < -PI) err += TWO_PI;
float m = TURN * DT;
if (err > m) err = m; if (err < -m) err = -m;
yaw += err;
fwd = ;
float c = llCos(want - yaw);
float top = SPEED + 0.6 * (dist - NEAR - 1.0);
if (top < SPEED) top = SPEED; if (top > RUN) top = RUN;
if (c > 0) v = top * c;
if (dist < NEAR + 0.6) v *= (dist - NEAR) / 0.6 + 0.2;
}
}
}
pos += fwd * (v * DT);
rotation rot = llEuler2Rot(<0, 0, yaw>);
integer i;
if (swingGroup >= 0 && llGetTime() - swingT0 >= SWING)
{
for (i = 0; i < 8; ++i) if (group_of(i) == swingGroup)
planted = llListReplaceList(planted, [llList2Vector(swingTo, i)], i, i);
swingGroup = -1;
}
if (swingGroup < 0)
{
float worst0 = 0; float worst1 = 0;
for (i = 0; i < 8; ++i)
{
vector h = llList2Vector(homes, i) * rot + pos;
vector f = llList2Vector(planted, i);
float lag = llVecDist(, );
if (group_of(i) == 0) { if (lag > worst0) worst0 = lag; }
else if (lag > worst1) worst1 = lag;
}
float thresh = STEP;
if (v == 0) thresh = 0.04;
integer g = -1;
if (worst0 > thresh && worst0 >= worst1) g = 0;
else if (worst1 > thresh) g = 1;
if (g >= 0)
{
swingGroup = g; swingT0 = llGetTime();
SWING = 0.2 - 0.025 * v; if (SWING < 0.12) SWING = 0.12;
swingFrom = planted; swingTo = planted;
float ld = v * SWING * 1.6; if (ld > 0.28) ld = 0.28;
for (i = 0; i < 8; ++i) if (group_of(i) == g)
{
vector h = llList2Vector(homes, i) * rot + pos + fwd * ld;
swingTo = llListReplaceList(swingTo, [], i, i);
}
}
}
float gz = 0;
for (i = 0; i < 8; ++i) gz += zof(llList2Vector(planted, i));
gz = gz / 8 + hNow;
pos.z += (gz - pos.z) * 0.35;
if (swingGroup >= 0) pos.z += 0.015 * llSin(PI * (llGetTime() - swingT0) / SWING);
pose(rot);
}
}