// Arm walker: a round furry creature with no legs that gets around on two long arms, Gorilla Tag style.
// Hands are planted in the world (llCastRay down); the body lurches between them on two-bone IK arms, one arm
// at a time, dipping and rolling toward the planted hand. Touch: follows you. Touch its face: chest pounding.
// Owner on /7: come, stay, pound, reset. Promised to a friend 2026-10-11; engine from the Walker Cat.
// Claude Opus 5.5 (Tessel).
float L1 = 0.68; float L2 = 0.68;
float H = 0.6; // body centre above the ground
float SPEED = 3.2; float TURN = 3.2; // gorilla tag monkes are FAST (a friend)
float STEP = 0.3; float SWING = 0.22; float DT = 0.1;
list shoulders = [<0.05, 0.27, 0.12>, <0.05, -0.27, 0.12>];
list homes = [<0.5, 0.42, 0>, <0.5, -0.42, 0>];
list poles = [<-0.4, 1, 0.3>, <-0.4, -1, 0.3>]; // elbows out and back
list upperL; list foreL; list handL;
list planted; list swingFrom; list swingTo;
integer swinging = -1; float swingT0;
vector pos; float yaw; key target = NULL_KEY; integer near;
float poundT0;
// bounce mode (fast): the body hops like a basketball; each hand runs a two-hop cycle, offset by one hop:
// planted and pushing for the first quarter, then swinging forward to where the body will be at its next plant
float HOP_T = 0.7; float HOP_H = 0.75; // about its own height (a friend's suggestion)
integer bouncing; float cycleT0; list lastPsi = [0.0, 0.0]; list plantAt; list swingStart; list targetZ;
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; }
float ground(vector p, float fallback)
{
list r = llCastRay(
, ,
[RC_REJECT_TYPES, RC_REJECT_AGENTS | RC_REJECT_PHYSICAL, 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));
return fallback;
}
vector hold(vector h, vector fwd, float fallback) // handhold near h, avoiding drops
{
float base = pos.z - H;
list offs = [0.0, 0.18, -0.15, 0.36];
vector best = ; float bestCost = 1e9;
integer k;
for (k = 0; k < 4; ++k)
{
float o = llList2Float(offs, k);
vector c = h + fwd * o;
float z = ground(c, -999);
if (z > -999)
{
float dz = llFabs(z - base);
float cost = llFabs(o) + 2.5 * dz;
if (dz > 0.45) cost += 3;
if (cost < bestCost) { bestCost = cost; best = ; }
}
}
return best;
}
list arm(integer i, vector handW, rotation rot)
{
vector sh = llList2Vector(shoulders, i);
vector d = (handW - pos) / rot - sh;
float dist = llVecMag(d);
vector dir = d / dist;
if (dist > L1 + L2 - 0.01) dist = L1 + L2 - 0.01;
if (dist < 0.15) dist = 0.15;
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, i);
vector p = pole - dir * (pole * dir);
if (llVecMag(p) < 0.01) p = <0, 0, 1>;
p = llVecNorm(p);
vector elbow = sh + dir * (L1 * llCos(a)) + p * (L1 * llSin(a));
vector end = sh + dir * dist;
vector up = <0, 0, 1>;
return [PRIM_LINK_TARGET, llList2Integer(upperL, i), PRIM_POS_LOCAL, (sh + elbow) / 2, PRIM_ROT_LOCAL, llRotBetween(up, llVecNorm(elbow - sh)),
PRIM_LINK_TARGET, llList2Integer(foreL, i), PRIM_POS_LOCAL, (elbow + end) / 2, PRIM_ROT_LOCAL, llRotBetween(up, llVecNorm(end - elbow)),
PRIM_LINK_TARGET, llList2Integer(handL, i), PRIM_POS_LOCAL, end + <0, 0, 0.03>, PRIM_ROT_LOCAL, ZERO_ROTATION];
}
pose(rotation body, list hands)
{
list p = [PRIM_POSITION, pos, PRIM_ROTATION, body];
p += arm(0, llList2Vector(hands, 0), body);
p += arm(1, llList2Vector(hands, 1), body);
llSetLinkPrimitiveParamsFast(LINK_ROOT, p);
}
default
{
state_entry()
{
integer i;
upperL = [link_named("l_upper"), link_named("r_upper")];
foreL = [link_named("l_fore"), link_named("r_fore")];
handL = [link_named("l_hand"), link_named("r_hand")];
llSetLinkPrimitiveParamsFast(link_named("face"), [PRIM_POS_LOCAL, <0.255, 0, 0.04>,
PRIM_LINK_TARGET, link_named("eye_l"), PRIM_POS_LOCAL, <0.315, 0.075, 0.1>,
PRIM_LINK_TARGET, link_named("eye_r"), PRIM_POS_LOCAL, <0.315, -0.075, 0.1>,
PRIM_LINK_TARGET, link_named("mouth"), PRIM_POS_LOCAL, <0.318, 0, -0.03>]);
for (i = 2; i <= llGetNumberOfPrims(); ++i) // children don't collide: lower LI, and hand rays skip them
llSetLinkPrimitiveParamsFast(i, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_NONE]);
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 < 2; ++i)
{
vector h = llList2Vector(homes, i) * rot + pos;
planted += ;
}
swingFrom = planted; swingTo = planted;
llListen(7, "", llGetOwner(), "");
llSetTimerEvent(DT);
}
on_rez(integer n) { llResetScript(); }
changed(integer c) { if (c & CHANGED_LINK) llResetScript(); }
touch_start(integer n)
{
integer ln = llDetectedLinkNumber(0);
string nm = llGetLinkName(ln);
if (nm == "face" || nm == "eye_l" || nm == "eye_r" || nm == "mouth") { poundT0 = llGetTime(); return; }
key who = llDetectedKey(0);
if (target == who) { target = NULL_KEY; llWhisper(0, "*plops down*"); }
else { target = who; llWhisper(0, "*knuckle-walks 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 == "pound") poundT0 = llGetTime();
else if (msg == "reset") llResetScript();
}
timer()
{
vector fwd = ;
rotation rot = llEuler2Rot(<0, 0, yaw>);
integer i;
float now = llGetTime();
if (poundT0 > 0) // chest pounding: hands drum the front of the body
{
float t = now - poundT0;
if (t > 1.8) poundT0 = 0;
else
{
rotation body = llEuler2Rot(<0, -0.25, 0>) * rot; // sits up a little
float a = llSin(t * 18); float b = llSin(t * 18 + PI);
if (a < 0) a = 0; if (b < 0) b = 0;
list hands = [pos + <0.36 + 0.12 * a, 0.12, 0.08> * body, pos + <0.36 + 0.12 * b, -0.12, 0.08> * body];
pose(body, hands);
return;
}
}
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;
integer wasNear = near;
near = llVecMag(to) <= 2.0;
if (near && !wasNear && llFrand(1) < 0.4) poundT0 = now; // made it! sometimes celebrates
if (!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 = ; rot = llEuler2Rot(<0, 0, yaw>);
float c = llCos(want - yaw);
float dist = llVecMag(to);
float sp = SPEED; if (dist < 5) sp = 1.2 + (SPEED - 1.2) * (dist - 2) / 3;
if (c > 0) v = sp * c;
}
}
}
if (v > 1.4)
{
if (!bouncing)
{
DT = 0.05; llSetTimerEvent(DT); // smoother arcs while airborne
bouncing = TRUE; cycleT0 = now; lastPsi = [0.0, 0.5];
plantAt = planted; swingStart = planted; targetZ = [zof(llList2Vector(planted, 0)), zof(llList2Vector(planted, 1))];
}
pos += fwd * (v * DT);
float ph = (now - cycleT0) / HOP_T; ph -= (integer)ph;
float baseZ = (llList2Float(targetZ, 0) + llList2Float(targetZ, 1)) / 2 + H - 0.12;
pos.z = baseZ + HOP_H * 4 * ph * (1 - ph);
list hands = [];
for (i = 0; i < 2; ++i)
{
float psi = (now - cycleT0) / (2 * HOP_T) + 0.5 * i; psi -= (integer)psi;
if (psi < llList2Float(lastPsi, i)) // wrapped: this hand lands now
{
vector hp = llList2Vector(homes, i) * rot + pos + <0, 0, 0>;
vector land = ;
plantAt = llListReplaceList(plantAt, [land], i, i);
targetZ = llListReplaceList(targetZ, [land.z], i, i);
}
else if (psi >= 0.25 && llList2Float(lastPsi, i) < 0.25) // lifts off
swingStart = llListReplaceList(swingStart, [llList2Vector(plantAt, i)], i, i);
lastPsi = llListReplaceList(lastPsi, [psi], i, i);
vector hnd = llList2Vector(plantAt, i);
if (psi >= 0.25)
{
float u = (psi - 0.25) / 0.75;
float e = u * u * (3 - 2 * u);
vector fut = pos + fwd * (v * (1 - psi) * 2 * HOP_T) + llList2Vector(homes, i) * rot;
fut.z = llList2Float(targetZ, i);
vector a = llList2Vector(swingStart, i);
hnd = a + (fut - a) * e + <0, 0, 0.35 * llSin(PI * u)>;
}
hands += hnd;
}
planted = hands; swinging = -1;
pose(llEuler2Rot(<0, 0.3 - 0.35 * ph, 0>) * rot, hands);
return;
}
if (bouncing) { DT = 0.1; llSetTimerEvent(DT); bouncing = FALSE; pos.z = (llList2Float(targetZ, 0) + llList2Float(targetZ, 1)) / 2 + H; }
// each stroke launches the body: fastest while a hand swings forward through the air
float surge = 0.6;
if (swinging >= 0) surge = 1.3;
pos += fwd * (v * surge * DT);
if (swinging >= 0 && now - swingT0 >= SWING)
{
planted = llListReplaceList(planted, [llList2Vector(swingTo, swinging)], swinging, swinging);
swinging = -1;
}
if (swinging < 0)
{
float lag0 = 0; float lag1 = 0;
for (i = 0; i < 2; ++i)
{
vector h = llList2Vector(homes, i) * rot + pos;
vector f = llList2Vector(planted, i);
float lag = llVecDist(, );
if (i == 0) lag0 = lag; else lag1 = lag;
}
float thresh = STEP;
if (v == 0) thresh = 0.08;
integer g = -1;
if (lag0 > thresh && lag0 >= lag1) g = 0;
else if (lag1 > thresh) g = 1;
if (g >= 0)
{
swinging = g; swingT0 = now; swingFrom = planted; swingTo = planted;
vector h = llList2Vector(homes, g) * rot + pos + fwd * (v * SWING * 1.3);
swingTo = llListReplaceList(swingTo, [hold(h, fwd, zof(llList2Vector(planted, g)))], g, g);
}
}
float gz = (zof(llList2Vector(planted, 0)) + zof(llList2Vector(planted, 1))) / 2 + H;
float dip = 0; float roll = 0;
list hands = planted;
if (swinging >= 0)
{
float s = (now - swingT0) / SWING; if (s > 1) s = 1;
float e = s * s * (3 - 2 * s);
vector a = llList2Vector(swingFrom, swinging); vector b = llList2Vector(swingTo, swinging);
hands = llListReplaceList(hands, [a + (b - a) * e + <0, 0, 0.3 * llSin(PI * s)>], swinging, swinging);
dip = -0.06 * llSin(PI * s);
if (v > 1.5) dip = -0.3 * llSin(PI * s); // a hop per stroke
roll = 0.14 * llSin(PI * s);
if (swinging == 1) roll = -roll; // lean onto the planted hand
}
pos.z += (gz - dip - pos.z) * 0.6;
float lean = 0.12;
if (v > 0) lean = 0.22;
pose(llEuler2Rot() * rot, hands);
}
}