// Tessel Node: makes the linkset it sits in match a job, or exports it. // The workbench remote-loads it into a root prim (or slbuild.py writes it in over the editor // RPC) after one or more generated "Tessel Job" part scripts. Each part holds job lines and // applies its own: // H|1||||||| (first line of part 1) // L| then P||... lines of llSetLinkPrimitiveParamsFast values // The node reads the header (link messages), moves the root, grows or shrinks the linkset to // (PERMISSION_CHANGE_LINKS, seeds from the workbench), then has each part apply itself. // Modes: build, apply (same thing), export (dumps every link's params to the owner as chat). // Talks to the owner as "TB|||..." lines, then removes itself and the spare seed // unless flags say keep. Start param -1 means: delete this object (used on unlinked prims). // Standard LSL only (no os*). Written by Claude Opus 5.5, 2026-10-09. integer PIN = 7340881; integer WB_CH = -7340882; integer TO_PARTS = 7340; integer FROM_PARTS = 7341; integer DIE = -1; string SEED = "Tessel Seed"; // name prefix integer F_POS = 1; integer F_GROUND = 2; integer F_KEEP_NODE = 4; integer F_KEEP_SEED = 8; string gNonce = "0"; string gMode; integer gWant; integer gFlags; vector gPos; float gZLift; integer gParts; integer gHeard; integer gStarted; integer gToRez; integer gLinking; integer gApplied; float gT0; string seed() { integer i; for (i = 0; i < llGetInventoryNumber(INVENTORY_OBJECT); ++i) { string n = llGetInventoryName(INVENTORY_OBJECT, i); if (llSubStringIndex(n, SEED) == 0) return n; } return ""; } say(string s) { llOwnerSay("TB|" + gNonce + "|" + s); } integer prims() { return llGetObjectPrimCount(llGetKey()); } finish(string what) { llSetTimerEvent(0); say(what); llMessageLinked(LINK_THIS, TO_PARTS, "abort", ""); if (!(gFlags & F_KEEP_SEED) && seed() != "") llRemoveInventory(seed()); if (!(gFlags & F_KEEP_NODE)) llRemoveInventory(llGetScriptName()); } string enc(list l) { string s; integer n = llGetListLength(l); integer i; for (i = 0; i < n; ++i) { integer t = llGetListEntryType(l, i); if (t == TYPE_INTEGER) s += "|i" + (string)llList2Integer(l, i); else if (t == TYPE_FLOAT) s += "|f" + (string)llList2Float(l, i); else if (t == TYPE_VECTOR) s += "|v" + (string)llList2Vector(l, i); else if (t == TYPE_ROTATION) s += "|r" + (string)llList2Rot(l, i); else if (t == TYPE_KEY) s += "|k" + llList2String(l, i); else s += "|s" + llEscapeURL(llList2String(l, i)); } return s; } start() { gStarted = TRUE; say("started|" + gMode + "|" + (string)llGetKey() + "|" + (string)prims()); if (gMode == "export") { doExport(); return; } if (gMode != "build" && gMode != "apply") { finish("error|unknown mode " + gMode); return; } if (gFlags & F_POS) { vector p = gPos; if (gFlags & F_GROUND) p.z = llGround(p - llGetPos()) + gZLift; if (!llSetRegionPos(p)) say("warn|could not move to " + (string)p); } reshape(); } // Grow or shrink the linkset to gWant prims, then apply the parts. reshape() { integer have = prims(); if (have == gWant) { applyPart(1); return; } if (!(llGetPermissions() & PERMISSION_CHANGE_LINKS)) { say("perm"); llRequestPermissions(llGetOwner(), PERMISSION_CHANGE_LINKS); return; // run_time_permissions resumes } if (have > gWant) { integer n; for (n = have; n > gWant; --n) { key k = llGetLinkKey(n); llBreakLink(n); // Prims rezzed from seeds carry the pin and die; the CLI deletes any that don't. llRemoteLoadScriptPin(k, llGetScriptName(), PIN, TRUE, DIE); say("unlinked|" + (string)k); } applyPart(1); return; } if (seed() == "") { say("needseed"); llRegionSay(WB_CH, "seed"); llSetTimerEvent(15); return; // changed(CHANGED_INVENTORY) resumes } gToRez = gWant - have; gLinking = TRUE; say("rezzing|" + (string)gToRez); llSetTimerEvent(30 + 3 * gToRez); llRezObject(seed(), llGetPos(), ZERO_VECTOR, ZERO_ROTATION, -1); } applyPart(integer k) { if (k > gParts) { finish("done|" + (string)llGetKey() + "|" + (string)prims() + "|" + (string)gApplied + "|" + (string)(llGetTime() - gT0)); return; } llMessageLinked(LINK_THIS, TO_PARTS, "apply|" + (string)k, ""); } doExport() { integer n = prims(); list base = [PRIM_NAME, PRIM_DESC, PRIM_TYPE, PRIM_SLICE, PRIM_SIZE, PRIM_POS_LOCAL, PRIM_ROT_LOCAL, PRIM_TEXT, PRIM_POINT_LIGHT, PRIM_PHYSICS_SHAPE_TYPE, PRIM_FLEXIBLE]; list face = [PRIM_COLOR, PRIM_TEXTURE, PRIM_GLOW, PRIM_FULLBRIGHT, PRIM_BUMP_SHINY, PRIM_TEXGEN, PRIM_ALPHA_MODE]; // PBR material ids only exist on Second Life. if (llSubStringIndex(llGetEnv("sim_channel"), "OpenSim") < 0) face += [PRIM_RENDER_MATERIAL]; integer nb = llGetListLength(base); integer nf = llGetListLength(face); string buf = "H|" + (string)n + enc([llGetPos(), llGetRot(), llGetObjectName(), llGetObjectDesc()]); integer seq; integer i; for (i = 1; i <= n; ++i) { integer link = i; if (n == 1) link = LINK_THIS; string line = "B|" + (string)i; integer j; for (j = 0; j < nb; ++j) { integer code = llList2Integer(base, j); line += "|#" + (string)code + enc(llGetLinkPrimitiveParams(link, [code])); } list lines = [line]; integer sides = llGetLinkNumberOfSides(link); string prev; integer f; for (f = 0; f < sides; ++f) { string fl; for (j = 0; j < nf; ++j) { integer code = llList2Integer(face, j); fl += "|#" + (string)code + enc(llGetLinkPrimitiveParams(link, [code, f])); } if (fl == prev) lines += "F|" + (string)i + "|" + (string)f + "|="; else lines += "F|" + (string)i + "|" + (string)f + fl; prev = fl; } integer k; for (k = 0; k < llGetListLength(lines); ++k) { string l = llList2String(lines, k); if (llStringLength(buf) + llStringLength(l) > 900) { say("x|" + (string)(seq++) + "\n" + buf); buf = ""; } if (buf != "") buf += "\n"; buf += l; } } say("x|" + (string)(seq++) + "\n" + buf); finish("exported|" + (string)seq); } default { state_entry() { if (llGetStartParameter() == DIE) { llDie(); return; } if (llGetInventoryType("Tessel Workbench") == INVENTORY_SCRIPT) return; // the bench's own copy gT0 = llGetTime(); llMessageLinked(LINK_THIS, TO_PARTS, "hello", ""); llSetTimerEvent(10); } link_message(integer sender, integer num, string msg, key id) { if (num != FROM_PARTS) return; list a = llParseStringKeepNulls(msg, ["|"], []); string what = llList2String(a, 0); if (what == "hi" && !gStarted) { ++gHeard; if (llList2String(a, 1) == "1") { // hi|1|H|1|mode|nonce|prims|flags|pos|zlift|parts gMode = llList2String(a, 4); gNonce = llList2String(a, 5); gWant = (integer)llList2String(a, 6); gFlags = (integer)llList2String(a, 7); gPos = (vector)llDeleteSubString(llList2String(a, 8), 0, 0); gZLift = (float)llList2String(a, 9); gParts = (integer)llList2String(a, 10); } if (gParts > 0 && gHeard >= gParts) { llSetTimerEvent(0); start(); } } else if (what == "applied") { gApplied += (integer)llList2String(a, 2); applyPart((integer)llList2String(a, 1) + 1); } } run_time_permissions(integer p) { if (p & PERMISSION_CHANGE_LINKS) reshape(); else finish("error|link permission refused"); } object_rez(key id) { llCreateLink(id, TRUE); if (--gToRez > 0) llRezObject(seed(), llGetPos(), ZERO_VECTOR, ZERO_ROTATION, -1); } changed(integer c) { if ((c & CHANGED_LINK) && gLinking && gToRez == 0 && prims() >= gWant) { gLinking = FALSE; llSetTimerEvent(0); applyPart(1); } if ((c & CHANGED_INVENTORY) && gStarted && !gLinking && seed() != "" && prims() < gWant) { llSetTimerEvent(0); reshape(); } } timer() { llSetTimerEvent(0); if (!gStarted) finish("error|no job part scripts answered"); else if (gLinking) finish("error|linking stalled at " + (string)prims() + " of " + (string)gWant + " prims"); else if (prims() < gWant) finish("error|no seed (is the workbench in this region?)"); } }