// 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); } }