1οΈβƒ£πŸŒ“πŸŒŽ
(πŸ“ƒ),(πŸ–ΌοΈ)

🌞 The Sun is currently in 'Golden Hour Serenade' phase! πŸŒ†
Gregorian: 10/12/2026
Julian: 2461326 -> 09/29/2026
AE Calendar: AE 3, Month 12, Day 6 (Monday)

Moon Phase: Waxing Crescent πŸŒ’
Species: Folf 🦊🐺
Were-Form: WereFolf πŸ¦ŠπŸŒ™
Consciousness: 1.4354893328814708/26 (5.5211128187748875%)
Miade-Score/Infini-Vaeria Consciousness: 0.9447888718122511% (1.9468337284117974%)



120πŸ•°οΈ11:12 PST

πŸŒ™ Moon Phase: New Moon πŸŒ‘
πŸ•’ UTC Time: 2026-10-12 01:32:02 UTC
πŸ“† Moon Age: 0.68 days




TIDE-SL-LSL-Mindweave

πŸ”—(55) | All
πŸ“…2026-09-23 00:36:45 -0700
β²οΈπŸ”2026-09-23 00:39:11 -0700 | ✍️infinivaeria | 🏷️[mindweave] 
(πŸͺŸ)

// RustForth
// LSL client for Tide-style REST API endpoints.

// =========================
// ESCAPE / UNESCAPE
// (kept exactly as provided by you)
// =========================

string ESCAPE_CHARACTER = "\\";
string ESCAPE_CHARACTER_REPLACE = "\\\\";

string escape(string avatar_message)
{
       list avatar_message_string = llParseString2List(avatar_message, [""], []); 
            integer counter = 0;            
            list string_chars = [];
            for (counter = 0; counter < llStringLength(avatar_message); counter++)
            {
                string_chars = string_chars + llGetSubString(avatar_message, counter, counter);
                if (llGetSubString(avatar_message, counter, counter) == ESCAPE_CHARACTER)
                {
                    string_chars = llListReplaceList(string_chars, ["/"], counter, counter );    
                }

                 else if (llGetSubString(avatar_message, counter, counter) == ":")
                {
                    string_chars = llListReplaceList(string_chars, ["\\:"], counter, counter );

                }

                  else if (llGetSubString(avatar_message, counter, counter) == "[")
                {
                    string_chars = llListReplaceList(string_chars, ["\\["], counter, counter );

                }


                    else if (llGetSubString(avatar_message, counter, counter) == "]")
                {
                    string_chars = llListReplaceList(string_chars, ["\\]"], counter, counter );
                  //  llSay(0, (string)string_chars);
                }

                      else if (llGetSubString(avatar_message, counter, counter) == "{")
                {
                    string_chars = llListReplaceList(string_chars, ["\\{"], counter, counter );
                  //  llSay(0, (string)string_chars);
                }


                       else if (llGetSubString(avatar_message, counter, counter) == "}")
                {
                    string_chars = llListReplaceList(string_chars, ["\\}"], counter, counter );
                   // llSay(0, (string)string_chars);
                }

                        else if (llGetSubString(avatar_message, counter, counter) == "'")
                {
                    string_chars = llListReplaceList(string_chars, ["\\'"], counter, counter );
                }
                        else if (llGetSubString(avatar_message, counter, counter) == "\"")
                {
                    string_chars = llListReplaceList(string_chars, ["\\\""], counter, counter );
                }

                 else if (llGetSubString(avatar_message, counter, counter) == ".")
                {
                    string_chars = llListReplaceList(string_chars, ["\\."], counter, counter );
                }

                }
            return llDumpList2String(string_chars, "");
    }

string unescape(string avatar_message)
{
          list avatar_message_string = llParseString2List(avatar_message, [""], []);
            integer counter = 0;            
            list string_chars = [];
            for (counter = 0; counter < llStringLength(avatar_message); counter++)
            {
                string_chars = string_chars + llGetSubString(avatar_message, counter, counter);
                if (llGetSubString(avatar_message, counter, counter) == "\\/")
                {
                    string_chars = llListReplaceList(string_chars, [ESCAPE_CHARACTER], counter, counter );
                    //llSay(0, (string)string_chars);
                }

                 else if (llGetSubString(avatar_message, counter, counter) == "\\:")
                {
                    string_chars = llListReplaceList(string_chars, [":"], counter, counter );
                   // llSay(0, (string)string_chars);
                }

                  else if (llGetSubString(avatar_message, counter, counter) == "\\[")
                {
                    string_chars = llListReplaceList(string_chars, ["["], counter, counter );
                   // llSay(0, (string)string_chars);
                }


                    else if (llGetSubString(avatar_message, counter, counter) == "\\]")
                {
                    string_chars = llListReplaceList(string_chars, ["]"], counter, counter );
                  //  llSay(0, (string)string_chars);
                }

                      else if (llGetSubString(avatar_message, counter, counter) == "\\{")
                {
                    string_chars = llListReplaceList(string_chars, ["{"], counter, counter );
                  //  llSay(0, (string)string_chars);
                }


                       else if (llGetSubString(avatar_message, counter, counter) == "\\}")
                {
                    string_chars = llListReplaceList(string_chars, ["}"], counter, counter );
                  //  llSay(0, (string)string_chars);
                }


            }
            return llDumpList2String(string_chars, ""); 
}


// =========================
// GLOBALS
// =========================

string SERVERURL = "https://stimky.info";
string API_KEY = "";
string BRIDGE_TOKEN = ""; // Set this to the MSSL_FORTH_BRIDGE_TOKEN value.

list requests = [];
list POST_HEADERS = [
    HTTP_METHOD, "POST",
    HTTP_MIMETYPE, "application/json"
];

list kv = []; // local cache

integer CHANNEL_OUTPUT = 0; // public channel
integer CHANNEL_INPUT = 1111; // set variables etc to a different channel
integer BRIDGE_ENABLED = FALSE;
integer BRIDGE_POLL_IN_FLIGHT = FALSE;
float BRIDGE_POLL_SECONDS = 3.0;
string SESSION_ID = "";
key notecard_query = NULL_KEY;
integer notecard_line = 0;
string notecard_name = "";
string notecard_source = "";
// =========================
// KV helpers (added β€” required by http_response)
// =========================

string kv_get(string keyVal)
{
    integer i = llListFindList(kv, [keyVal]);
    if (i != -1 && (i + 1) < llGetListLength(kv)) return llList2String(kv, i + 1);
    return "";
}

kv_set(string keyVal, string value)
{
    integer i = llListFindList(kv, [keyVal]);
    if (i != -1)
    {
        kv = llListReplaceList(kv, [value], i + 1, i + 1);
    }
    else
    {
        kv += [keyVal, value];
    }
}

kv_delete(string keyVal)
{
    integer i = llListFindList(kv, [keyVal]);
    if (i != -1) kv = llDeleteSubList(kv, i, i + 1);
}

// =========================
// OUTPUT HELPERS
// =========================

say(string msg)
{
    llSay(CHANNEL_OUTPUT, msg);
}

say_chunks(integer channel, string msg)
{
    integer start = 0;
    integer length = llStringLength(msg);
    while (start < length)
    {
        llSay(channel, llGetSubString(msg, start, start + 899));
        start += 900;
    }
}

debug(string msg)
{
    llOwnerSay(msg);
}

show_help()
{
    debug("RubyForth/Forth: separate sequential statements with ';'. Example: /1111 ruby count = 1; puts count; count += 1; puts count");
    debug("In a RubyForth notecard, use: forth do <Forth source> end. Both languages share variables and the active session.");
    debug("Session commands: /1111 session <safe-name> | /1111 session-reset. The default session is this prim UUID.");
}

forth_post(string endpoint, string action, string argument, string payload)
{
    payload = llJsonSetValue(payload, ["session_id"], SESSION_ID);
    key request_id = llHTTPRequest(SERVERURL + endpoint, POST_HEADERS, payload);
    requests += [(string)request_id, action, argument];
}

string forth_name_payload(string name)
{
    return llJsonSetValue("{}", ["name"], name);
}

string forth_file_payload(string name, string content)
{
    string json = llJsonSetValue("{}", ["name"], name);
    return llJsonSetValue(json, ["content"], content);
}

string forth_source_payload(string source, integer max_steps)
{
    string json = llJsonSetValue("{}", ["source"], source);
    if (max_steps > 0) json = llJsonSetValue(json, ["max_steps"], (string)max_steps);
    return json;
}

string forth_name_steps_payload(string name, integer max_steps)
{
    string json = llJsonSetValue("{}", ["name"], name);
    return llJsonSetValue(json, ["max_steps"], (string)max_steps);
}

string forth_bridge_payload()
{
    return llJsonSetValue("{}", ["token"], BRIDGE_TOKEN);
}

bridge_poll()
{
    if (!BRIDGE_ENABLED || BRIDGE_POLL_IN_FLIGHT || llStringLength(BRIDGE_TOKEN) == 0) return;
    BRIDGE_POLL_IN_FLIGHT = TRUE;
    forth_post("/forth/bridge/poll", "bridge", "", forth_bridge_payload());
}

float color_component(string value)
{
    float component = (float)value;
    if (component > 1.0) component = component / 255.0;
    if (component < 0.0) return 0.0;
    if (component > 1.0) return 1.0;
    return component;
}

float alpha_component(string value)
{
    return color_component(value);
}

run_forth_calls(string body)
{
    integer index = 0;
    string call = llJsonGetValue(body, ["calls", index]);

    while (call != JSON_INVALID)
    {
        string operation = llJsonGetValue(call, ["op"]);
        if (operation == "say")
        {
            llSay((integer)llJsonGetValue(call, ["channel"]), llJsonGetValue(call, ["message"]));
        }
        else if (operation == "whisper")
        {
            llWhisper((integer)llJsonGetValue(call, ["channel"]), llJsonGetValue(call, ["message"]));
        }
        else if (operation == "shout")
        {
            llShout((integer)llJsonGetValue(call, ["channel"]), llJsonGetValue(call, ["message"]));
        }
        else if (operation == "region_say")
        {
            llRegionSay((integer)llJsonGetValue(call, ["channel"]), llJsonGetValue(call, ["message"]));
        }
        else if (operation == "owner_say")
        {
            llOwnerSay(llJsonGetValue(call, ["message"]));
        }
        else if (operation == "set_text")
        {
            llSetText(
                llJsonGetValue(call, ["text"]),
                <color_component(llJsonGetValue(call, ["red"])), color_component(llJsonGetValue(call, ["green"])), color_component(llJsonGetValue(call, ["blue"]))>,
                alpha_component(llJsonGetValue(call, ["alpha"]))
            );
        }
        else if (operation == "set_color")
        {
            llSetColor(
                <color_component(llJsonGetValue(call, ["red"])), color_component(llJsonGetValue(call, ["green"])), color_component(llJsonGetValue(call, ["blue"]))>,
                (integer)llJsonGetValue(call, ["face"])
            );
        }
        else if (operation == "set_alpha")
        {
            llSetAlpha(alpha_component(llJsonGetValue(call, ["alpha"])), (integer)llJsonGetValue(call, ["face"]));
        }
        else if (operation == "play_sound")
        {
            llPlaySound(llJsonGetValue(call, ["sound"]), alpha_component(llJsonGetValue(call, ["volume"])));
        }
        else if (operation == "set_timer")
        {
            llSetTimerEvent((float)llJsonGetValue(call, ["seconds"]));
        }
        else if (operation == "set_region_pos")
        {
            llSetRegionPos(<(float)llJsonGetValue(call, ["x"]), (float)llJsonGetValue(call, ["y"]), (float)llJsonGetValue(call, ["z"])>);
        }
        else if (operation == "link_message")
        {
            llMessageLinked(
                (integer)llJsonGetValue(call, ["link"]),
                (integer)llJsonGetValue(call, ["code"]),
                llJsonGetValue(call, ["message"]),
                (key)llJsonGetValue(call, ["id"])
            );
        }
        index++;
        call = llJsonGetValue(body, ["calls", index]);
    }
}

// =========================
// DEFAULT STATE
// =========================

default
{

    on_rez(integer start_parameter)
    {
     llOwnerSay("Reloading variable functions for the tide to second life server interaction... Input on /1111 ..");   
    }

    state_entry()
    {
        SESSION_ID = (string)llGetKey();
        llListen(CHANNEL_INPUT, "", NULL_KEY, "");
        debug("Tide stack ready. Use /1111 help for RubyForth, Forth, session, and bridge syntax.");
    }

    listen(integer chan, string name, key id, string msg)
    {
        if (id != llGetOwner()) return;

        list parts = llParseString2List(msg, [" "], []);
        integer n = llGetListLength(parts);
        if (n == 0) return;

        string cmd = llToLower(llList2String(parts, 0));

        // =========================
        // CHANNEL SWITCH
        // =========================
        if (cmd == "channel" && n >= 2)
        {
            CHANNEL_OUTPUT = (integer)llList2String(parts, 1);
            //debug("Output channel set to " + (string)CHANNEL_OUTPUT);
            return;
        }

        if (cmd == "help")
        {
            show_help();
            return;
        }

        if (cmd == "session" && n == 2)
        {
            SESSION_ID = llList2String(parts, 1);
            debug("Active language session: " + SESSION_ID);
            return;
        }

        if (cmd == "session-reset")
        {
            SESSION_ID = (string)llGetKey();
            debug("Active language session reset to prim UUID: " + SESSION_ID);
            return;
        }

        if (cmd == "bridge-on")
        {
            if (llStringLength(BRIDGE_TOKEN) == 0)
            {
                debug("Set BRIDGE_TOKEN in this script before enabling the Second Life bridge.");
                return;
            }
            BRIDGE_ENABLED = TRUE;
            llSetTimerEvent(BRIDGE_POLL_SECONDS);
            bridge_poll();
            return;
        }

        if (cmd == "bridge-off")
        {
            BRIDGE_ENABLED = FALSE;
            BRIDGE_POLL_IN_FLIGHT = FALSE;
            llSetTimerEvent(0.0);
            return;
        }

        if (cmd == "bridge-poll")
        {
            bridge_poll();
            return;
        }

        // =========================
        // SET
        // =========================
        if (cmd == "set" && n >= 3)
        {
            string keyVal = escape(llList2String(parts, 1));
            string value  = escape(llDumpList2String(llList2List(parts, 2, -1), " "));

            string json = "{}";
            json = llJsonSetValue(json, [keyVal], value);
            json = llJsonSetValue(json, ["session_id"], SESSION_ID);

            key req = llHTTPRequest(SERVERURL + "/vars/set", POST_HEADERS, json);
            requests += [(string)req, "set", keyVal];

            //say("Sent set request for " + keyVal);
            return;
        }

        // =========================
        // GET
        // =========================
        if (cmd == "get" && n >= 2)
        {
            string keyVal = escape(llList2String(parts, 1));

            string json = "{}";
            json = llJsonSetValue(json, ["name"], keyVal);
            json = llJsonSetValue(json, ["session_id"], SESSION_ID);

            key req = llHTTPRequest(SERVERURL + "/vars/get", POST_HEADERS, json);
            requests += [(string)req, "get", keyVal];

            //say("Sent get request for " + keyVal);
            return;
        }

        // =========================
        // VIEW
        // =========================
        if (cmd == "view")
        {
            string json = llJsonSetValue("{}", ["action"], "view");
            json = llJsonSetValue(json, ["session_id"], SESSION_ID);

            key req = llHTTPRequest(SERVERURL + "/vars/view", POST_HEADERS, json);
            requests += [(string)req, "view", ""];

            //say("Sent view request");
            return;
        }

        // =========================
        // DELETE
        // =========================
        if (cmd == "delete" && n >= 2)
        {
            string keyVal = escape(llList2String(parts, 1));

            string json = llJsonSetValue("{}", ["name"], keyVal);
            json = llJsonSetValue(json, ["session_id"], SESSION_ID);

            key req = llHTTPRequest(SERVERURL + "/vars/delete", POST_HEADERS, json);
            requests += [(string)req, "delete", keyVal];

           // say("Sent delete request for " + keyVal);
            return;
        }

        // =========================
        // CLEAR
        // =========================
        if (cmd == "clear")
        {
            string json = llJsonSetValue("{}", ["action"], "clear");
            json = llJsonSetValue(json, ["session_id"], SESSION_ID);

            key req = llHTTPRequest(SERVERURL + "/vars/clear", POST_HEADERS, json);
            requests += [(string)req, "clear", ""];

            //say("Sent clear request");
            return;
        }

        // =========================
        // STATUS
        // =========================
        if (cmd == "status")
        {
            string json = llJsonSetValue("{}", ["action"], "status");
            json = llJsonSetValue(json, ["session_id"], SESSION_ID);

            key req = llHTTPRequest(SERVERURL + "/vars/status", POST_HEADERS, json);
            requests += [(string)req, "status", ""];

            //say("Sent status request");
            return;
        }

        // =========================
        // HISTORY
        // =========================
        if (cmd == "history")
        {
            string json = llJsonSetValue("{}", ["action"], "history");
            json = llJsonSetValue(json, ["session_id"], SESSION_ID);

            key req = llHTTPRequest(SERVERURL + "/vars/history", POST_HEADERS, json);
            requests += [(string)req, "history", ""];

            //say("Sent history request");
            return;
        }

        // Run Forth and announce its output on the selected output channel.
        if (cmd == "forth-steps" && n >= 3)
        {
            integer max_steps = (integer)llList2String(parts, 1);
            if (max_steps < 1 || max_steps > 1000000)
            {
                debug("max_steps must be between 1 and 1000000.");
                return;
            }
            string source = llDumpList2String(llList2List(parts, 2, -1), " ");
            forth_post("/forth/eval", "forth", (string)CHANNEL_OUTPUT, forth_source_payload(source, max_steps));
            return;
        }

        if (cmd == "forth" && n >= 2)
        {
            string source = llDumpList2String(llList2List(parts, 1, -1), " ");
            string json = llJsonSetValue("{}", ["source"], source);
            forth_post("/forth/eval", "forth", (string)CHANNEL_OUTPUT, json);
            return;
        }

        if (cmd == "hybrid" && n >= 2)
        {
            string source = llDumpList2String(llList2List(parts, 1, -1), " ");
            forth_post("/forth/eval", "forth", (string)CHANNEL_OUTPUT, forth_source_payload(source, 0));
            return;
        }

        // Run Forth and announce only this result on an arbitrary SL channel.
        if (cmd == "forth-say" && n >= 3)
        {
            integer reply_channel = (integer)llList2String(parts, 1);
            string source = llDumpList2String(llList2List(parts, 2, -1), " ");
            string json = llJsonSetValue("{}", ["source"], source);
            forth_post("/forth/eval", "forth", (string)reply_channel, json);
            return;
        }

        if (cmd == "hybrid-say" && n >= 3)
        {
            integer reply_channel = (integer)llList2String(parts, 1);
            string source = llDumpList2String(llList2List(parts, 2, -1), " ");
            forth_post("/forth/eval", "forth", (string)reply_channel, forth_source_payload(source, 0));
            return;
        }

        // Run Ruby-shaped source through the server's RubyForth compiler.
        if (cmd == "ruby-steps" && n >= 3)
        {
            integer max_steps = (integer)llList2String(parts, 1);
            if (max_steps < 1 || max_steps > 1000000)
            {
                debug("max_steps must be between 1 and 1000000.");
                return;
            }
            string source = llDumpList2String(llList2List(parts, 2, -1), " ");
            forth_post("/ruby/eval", "forth", (string)CHANNEL_OUTPUT, forth_source_payload(source, max_steps));
            return;
        }

        if (cmd == "ruby" && n >= 2)
        {
            string source = llDumpList2String(llList2List(parts, 1, -1), " ");
            string json = llJsonSetValue("{}", ["source"], source);
            forth_post("/ruby/eval", "forth", (string)CHANNEL_OUTPUT, json);
            return;
        }

        if (cmd == "ruby-say" && n >= 3)
        {
            integer reply_channel = (integer)llList2String(parts, 1);
            string source = llDumpList2String(llList2List(parts, 2, -1), " ");
            string json = llJsonSetValue("{}", ["source"], source);
            forth_post("/ruby/eval", "forth", (string)reply_channel, json);
            return;
        }

        if (cmd == "algebra" && n >= 2)
        {
            string expression = llDumpList2String(llList2List(parts, 1, -1), " ");
            string json = llJsonSetValue("{}", ["expression"], expression);
            forth_post("/forth/algebra", "algebra", (string)CHANNEL_OUTPUT, json);
            return;
        }

        if (cmd == "algebra-say" && n >= 3)
        {
            integer reply_channel = (integer)llList2String(parts, 1);
            string expression = llDumpList2String(llList2List(parts, 2, -1), " ");
            string json = llJsonSetValue("{}", ["expression"], expression);
            forth_post("/forth/algebra", "algebra", (string)reply_channel, json);
            return;
        }

        if (cmd == "matrix-get" && n == 2)
        {
            string matrix_name = llList2String(parts, 1);
            forth_post("/forth/matrices/get", "matrix", (string)CHANNEL_OUTPUT, forth_name_payload(matrix_name));
            return;
        }

        if (cmd == "matrix-get-say" && n == 3)
        {
            integer reply_channel = (integer)llList2String(parts, 1);
            string matrix_name = llList2String(parts, 2);
            forth_post("/forth/matrices/get", "matrix", (string)reply_channel, forth_name_payload(matrix_name));
            return;
        }

        if (cmd == "file-write" && n >= 3)
        {
            string file_name = llList2String(parts, 1);
            string content = llDumpList2String(llList2List(parts, 2, -1), " ");
            forth_post("/forth/files/write", "file-write", (string)CHANNEL_OUTPUT, forth_file_payload(file_name, content));
            return;
        }

        if (cmd == "file-read" && n == 2)
        {
            string file_name = llList2String(parts, 1);
            forth_post("/forth/files/read", "file-read", (string)CHANNEL_OUTPUT, forth_name_payload(file_name));
            return;
        }

        if (cmd == "file-read-say" && n == 3)
        {
            integer reply_channel = (integer)llList2String(parts, 1);
            string file_name = llList2String(parts, 2);
            forth_post("/forth/files/read", "file-read", (string)reply_channel, forth_name_payload(file_name));
            return;
        }

        if (cmd == "files")
        {
            forth_post("/forth/files/list", "files", (string)CHANNEL_OUTPUT, "{}");
            return;
        }

        if (cmd == "file-delete" && n == 2)
        {
            string file_name = llList2String(parts, 1);
            forth_post("/forth/files/delete", "file-delete", (string)CHANNEL_OUTPUT, forth_name_payload(file_name));
            return;
        }

        // Upload an inventory notecard as a named remote Forth program.
        if (cmd == "notecard" && n == 2)
        {
            string card_name = llList2String(parts, 1);
            if (llGetInventoryType(card_name) != INVENTORY_NOTECARD)
            {
                say("No notecard named " + card_name + " in this object.");
                return;
            }
            notecard_name = card_name;
            notecard_source = "";
            notecard_line = 0;
            notecard_query = llGetNotecardLine(notecard_name, notecard_line);
            say("Loading " + notecard_name + " into the remote Forth database.");
            return;
        }

        if (cmd == "forth-run" && n == 2)
        {
            string card_name = llList2String(parts, 1);
            forth_post("/forth/notecards/run", "forth", (string)CHANNEL_OUTPUT, forth_name_payload(card_name));
            return;
        }

        if (cmd == "forth-run-steps" && n == 3)
        {
            integer max_steps = (integer)llList2String(parts, 1);
            if (max_steps < 1 || max_steps > 1000000)
            {
                debug("max_steps must be between 1 and 1000000.");
                return;
            }
            string card_name = llList2String(parts, 2);
            forth_post("/forth/notecards/run", "forth", (string)CHANNEL_OUTPUT, forth_name_steps_payload(card_name, max_steps));
            return;
        }

        if (cmd == "forth-run-say" && n == 3)
        {
            integer reply_channel = (integer)llList2String(parts, 1);
            string card_name = llList2String(parts, 2);
            forth_post("/forth/notecards/run", "forth", (string)reply_channel, forth_name_payload(card_name));
            return;
        }

        if (cmd == "ruby-run" && n == 2)
        {
            string card_name = llList2String(parts, 1);
            forth_post("/ruby/notecards/run", "forth", (string)CHANNEL_OUTPUT, forth_name_payload(card_name));
            return;
        }

        if (cmd == "ruby-run-steps" && n == 3)
        {
            integer max_steps = (integer)llList2String(parts, 1);
            if (max_steps < 1 || max_steps > 1000000)
            {
                debug("max_steps must be between 1 and 1000000.");
                return;
            }
            string card_name = llList2String(parts, 2);
            forth_post("/ruby/notecards/run", "forth", (string)CHANNEL_OUTPUT, forth_name_steps_payload(card_name, max_steps));
            return;
        }

        if (cmd == "ruby-run-say" && n == 3)
        {
            integer reply_channel = (integer)llList2String(parts, 1);
            string card_name = llList2String(parts, 2);
            forth_post("/ruby/notecards/run", "forth", (string)reply_channel, forth_name_payload(card_name));
            return;
        }

        if (cmd == "notecards")
        {
            forth_post("/forth/notecards/list", "notecards", "", "{}");
            return;
        }

        if (cmd == "notecard-get" && n == 2)
        {
            string card_name = llList2String(parts, 1);
            forth_post("/forth/notecards/get", "notecard-get", card_name, forth_name_payload(card_name));
            return;
        }

        if (cmd == "notecard-delete" && n == 2)
        {
            string card_name = llList2String(parts, 1);
            forth_post("/forth/notecards/delete", "notecard-delete", card_name, forth_name_payload(card_name));
            return;
        }

        //debug("Unknown command: " + cmd);
    }

    // =========================
    // HTTP RESPONSE
    // =========================

    http_response(key request_id, integer status, list metadata, string body)
    {
        string rid = (string)request_id;
        integer idx = llListFindList(requests, [rid]);

        if (idx == -1)
        {
            //debug("HTTP response (unknown request): " + (string)status + " body: " + body);
            return;
        }

        string action = llList2String(requests, idx + 1);
        string arg    = llList2String(requests, idx + 2);

        requests = llDeleteSubList(requests, idx, idx + 2);

        //say("HTTP response for " + action + " (status " + (string)status + "):");

        if (llStringLength(body) == 0)
        {
            if (action == "bridge") BRIDGE_POLL_IN_FLIGHT = FALSE;
            //say("Empty body");
            return;
        }

        if (action == "bridge")
        {
            BRIDGE_POLL_IN_FLIGHT = FALSE;
            string source = llJsonGetValue(body, ["message", "source"]);
            if (source != JSON_INVALID)
            {
                string payload = llJsonSetValue("{}", ["source"], source);
                string max_steps = llJsonGetValue(body, ["message", "max_steps"]);
                if (max_steps != JSON_INVALID && max_steps != JSON_NULL)
                {
                    payload = llJsonSetValue(payload, ["max_steps"], max_steps);
                }
                string language = llJsonGetValue(body, ["message", "language"]);
                string endpoint = "/forth/eval";
                if (language == "ruby") endpoint = "/ruby/eval";
                forth_post(endpoint, "forth", (string)CHANNEL_OUTPUT, payload);
            }
        }
        else if (action == "forth")
        {
            string output = llJsonGetValue(body, ["output"]);
            integer reply_channel = (integer)arg;
            run_forth_calls(body);
            if (output != JSON_INVALID && llStringLength(output) > 0)
            {
                llSay(reply_channel, output);
            }
            else
            {
                llSay(reply_channel, body);
            }
        }
        else if (action == "file-read")
        {
            string content = llJsonGetValue(body, ["content"]);
            integer reply_channel = (integer)arg;
            if (content != JSON_INVALID)
            {
                say_chunks(reply_channel, content);
            }
            else
            {
                say_chunks(reply_channel, body);
            }
        }
        else if (action == "algebra")
        {
            integer reply_channel = (integer)arg;
            string simplified = llJsonGetValue(body, ["simplified"]);
            string derivative = llJsonGetValue(body, ["derivative"]);
            string integral = llJsonGetValue(body, ["integral"]);
            if (simplified != JSON_INVALID)
            {
                say_chunks(reply_channel, "f(x) = " + simplified + " | f'(x) = " + derivative + " | integral = " + integral);
            }
            else
            {
                say_chunks(reply_channel, body);
            }
        }
        else if (action == "matrix" || action == "files" || action == "file-write" || action == "file-delete")
        {
            say_chunks((integer)arg, body);
        }
        else
        {
            say_chunks(CHANNEL_OUTPUT, body);
        }

        if (action == "get")
        {
            string val = llJsonGetValue(body, [arg]);

            if (llStringLength(val) > 0)
            {
                kv_set(arg, unescape(val));
                //debug("Cached " + arg + " = " + val);
            }
        }
    }

    timer()
    {
        bridge_poll();
    }

    dataserver(key query_id, string data)
    {
        if (query_id != notecard_query) return;

        if (data != EOF)
        {
            notecard_source += data + "\n";
            ++notecard_line;
            notecard_query = llGetNotecardLine(notecard_name, notecard_line);
            return;
        }

        string json = llJsonSetValue("{}", ["name"], notecard_name);
        json = llJsonSetValue(json, ["source"], notecard_source);
        forth_post("/forth/notecards/save", "notecard-save", notecard_name, json);
        notecard_query = NULL_KEY;
        notecard_source = "";
    }
}

Tide Route Manual

This manual describes the Tide routes exposed by tiade-maeepers-saerver-all. The language runtime is bounded by a request step limit, but supports persistent state, loops, arrays, hashes, mixed Forth/RubyForth execution, named words, Second Life calls, and isolated sessions.

Conventions

  • Base URL: https://your-server.example
  • JSON POST routes require Content-Type: application/json.
  • Missing session_id uses the legacy default session.
  • Session IDs use 1-64 ASCII letters, digits, _, or -.
  • LSL defaults to the current prim UUID as its session.
  • max_steps defaults to 10,000 and may be set from 1 through 1,000,000.

Language Routes

Method Route Purpose
GET /forth Machine-readable language and route catalog.
GET /forth/ui Browser console for Forth, RubyForth, algebra, and bridge jobs.
GET /ruby RubyForth capability description.
POST /forth/eval Execute Forth. Body: source, optional max_steps, optional session_id.
POST /ruby/eval Compile and execute RubyForth. Body: source, optional max_steps, optional session_id.
POST /sessions/:session_id/forth/eval Execute Forth in the path-selected session.
POST /sessions/:session_id/ruby/eval Execute RubyForth in the path-selected session.
GET /sessions/:session_id/status Return values visible in one session.
POST /program/run Run the lower-level JSON register VM.
GET /program/example Return a register-program example.
POST /forth/algebra Simplify, differentiate, integrate, and optionally evaluate an integer polynomial.
GET /forth/example Return a Forth countdown example.

Example:

curl -sS https://your-server.example/sessions/workshop_1/ruby/eval \
  -H 'Content-Type: application/json' \
  --data '{"source":"count = 2; puts count; count += 3; puts count"}'

RubyForth and Forth share the selected session. RubyForth supports semicolon statement separation and multiline Forth blocks:

count = 2; puts count
forth do
  $count 3 + count=
  $count puts
end
puts count

Variable Routes

These are compatibility routes used by the LSL variable commands.

Method Route Body Purpose
POST /vars/set JSON values Set variables.
POST /vars/get {"name":"key"} Read a variable.
POST /vars/view optional action View variables.
POST /vars/delete {"name":"key"} Delete a variable.
POST /vars/clear optional action Clear variables.
POST /vars/history optional action Read variable history.
POST /vars/status optional action Read store status.

For new code, prefer /sessions/:session_id/... language routes so the session is explicit and cannot be confused with a normal variable name.

Notecards, Matrices, and Files

Notecards

Method Route Purpose
POST /forth/notecards/save Save name, source, and optional session_id.
POST /forth/notecards/get Read a named notecard in the selected session.
POST /forth/notecards/list List notecards in the selected session.
POST /forth/notecards/run Run a saved Forth notecard in the selected session.
POST /ruby/notecards/run Compile and run a saved RubyForth notecard in the selected session.
POST /forth/notecards/delete Delete a saved notecard in the selected session.

Matrices

Method Route Purpose
POST /forth/matrices/get Read a named persisted matrix.

Matrices are created and changed by Forth words such as matrix, mset, m.scale, m.solve, m.transpose, and m.show.

Sandboxed files

Method Route Purpose
POST /forth/files/write Write a UTF-8 file with name, content, and optional session_id.
POST /forth/files/read Read a named file in the selected session.
POST /forth/files/list List files in the selected session.
POST /forth/files/delete Delete a file in the selected session.

File names are safe basenames. Files are separate from the partitioned-array language state and are not automatically executable.

Second Life Bridge

Method Route Purpose
POST /forth/bridge/enqueue Queue Forth or RubyForth using token, source, optional language, optional max_steps, and optional session_id.
POST /forth/bridge/poll Authenticated LSL poll; returns one matching queued job.
POST /sl_logger Store incoming Second Life log data.

Set MSSL_FORTH_BRIDGE_TOKEN on the server and the same BRIDGE_TOKEN in the LSL object. Use /1111 bridge-on to start polling. The LSL client polls immediately, then on its timer interval, and prevents overlapping polls with BRIDGE_POLL_IN_FLIGHT.

A bridge job has three stages: queued, delivered, and executed. Delivery removes the job from the queue. If execution fails afterward, this LSL client does not requeue it. Use a session-specific, idempotent program for retryable work.

Static and Utility Routes

Route Purpose
/ Main site entry or redirect.
/ae, /time, /weather Legacy utility output.
/analytics Analytics output.
/bridge/*rest Browser bridge pages.
/chatlog Chat-log viewing.
/css, /fonts, /img, /js, /public Static assets.
/echo Echo a request body.
/file/add, /file/delete Legacy temporary-file utilities.
/flashcard Flashcard utility.
/paema Legacy PAEMA handler.
/parse_plink URL parsing utility.
/praexy-saerver Form relay.
/random, /random2 Random utilities.
/restart-servers Persist and signal restart; protect at the proxy layer.
/rneutri, /rneutrialg Legacy text/file routes.
/rustby Rustby integration.
/sigil-deck and /sigil-deck/* Sigil deck cards, uploads, images, and random selection.
/tiade/img/resize, /tiade/moon, /tiade/sun Namespaced legacy Tiade utilities.
/tiade-maepers/*rest Legacy iframe bridge pages.

Sigil deck subroutes include /sigil-deck/card/:id, /sigil-deck/card/:id/update, /sigil-deck/card/:id/delete, /sigil-deck/image/:filename, /sigil-deck/random, and /sigil-deck/upload.

Mounted Compatibility Families

The server also mounts larger compatibility families documented in README.md:

  • Admin: /admin/login, /admin, /admin/add, /admin/remove/:db_name, /admin/delete, /admin/reload, /admin/list, /admin/rehash/:db_name.
  • Blog: /blog/login, /blog/signup, /blog/logout, and /blog/:user/*.
  • Gallery: /gallery, upload routes, and /gallery/view/:user/*.
  • Gallery collections: /gallery/uwu/* and /gallery/owo/*.
  • Ollama relay: /ollama, /ollama/health, /chat/:team, /game/:team/:player, /history/:team, and /teams/:team/prompt.

LSL Quick Reference

/1111 help
/1111 channel 0
/1111 session workshop_1
/1111 ruby count = 1; puts count; count += 1; puts count
/1111 forth 2 3 + puts; : square dup * ; 4 square puts;
/1111 bridge-on
/1111 bridge-off
/1111 session-reset

For the internal request ledger, asynchronous HTTP lifecycle, notecard loader, and detailed bridge polling behavior, see LSL_CLIENT_REFERENCE.md.

Battle-Test Checklist

Run these checks against a temporary server or maintenance session before using production data.

Language and session isolation

BASE=https://your-server.example
curl -sS "$BASE/sessions/alpha/forth/eval" -H 'Content-Type: application/json' \
  --data '{"source":"7 counter=; $counter puts"}'
curl -sS "$BASE/sessions/beta/forth/eval" -H 'Content-Type: application/json' \
  --data '{"source":"$counter puts"}'
curl -sS "$BASE/sessions/alpha/status"

Expected behavior: Alpha reports 7, Beta reports 0, and Alpha status contains only counter: 7.

Variable, file, and notecard isolation

curl -sS "$BASE/vars/set" -H 'Content-Type: application/json' \
  --data '{"session_id":"alpha","note":"alpha-value"}'
curl -sS "$BASE/vars/get" -H 'Content-Type: application/json' \
  --data '{"session_id":"beta","name":"note"}'

curl -sS "$BASE/forth/files/write" -H 'Content-Type: application/json' \
  --data '{"session_id":"alpha","name":"shared.txt","content":"alpha-file"}'
curl -sS "$BASE/forth/files/read" -H 'Content-Type: application/json' \
  --data '{"session_id":"beta","name":"shared.txt"}'

curl -sS "$BASE/forth/notecards/save" -H 'Content-Type: application/json' \
  --data '{"session_id":"alpha","name":"CardA","source":"7 puts"}'
curl -sS "$BASE/forth/notecards/get" -H 'Content-Type: application/json' \
  --data '{"session_id":"beta","name":"CardA"}'

The Beta variable, file, and notecard reads should each return 404.

Bridge isolation

TOKEN=battle-token
curl -sS "$BASE/forth/bridge/enqueue" -H 'Content-Type: application/json' \
  --data "{\"token\":\"$TOKEN\",\"session_id\":\"alpha\",\"language\":\"forth\",\"source\":\"0 \\\"alpha job\\\" sl.owner_say\"}"
curl -sS "$BASE/forth/bridge/enqueue" -H 'Content-Type: application/json' \
  --data "{\"token\":\"$TOKEN\",\"session_id\":\"beta\",\"language\":\"forth\",\"source\":\"0 \\\"beta job\\\" sl.owner_say\"}"
curl -sS "$BASE/forth/bridge/poll" -H 'Content-Type: application/json' \
  --data "{\"token\":\"$TOKEN\",\"session_id\":\"beta\"}"
curl -sS "$BASE/forth/bridge/poll" -H 'Content-Type: application/json' \
  --data "{\"token\":\"$TOKEN\",\"session_id\":\"alpha\"}"

The Beta poll must return the Beta source, and the Alpha poll must return the Alpha source. An empty matching session returns message: null.

Second Life RustForth Service Guide

Relote.Stack.Service.lsl is an owner-controlled Second Life client for the Tide/RustForth server. It accepts chat commands, sends HTTPS requests to the server, and can perform allowlisted server-generated actions in the object that contains the script.

This guide covers chat commands, Forth, RubyForth, persisted notecards, matrices, files, and the optional browser-to-Second-Life bridge.

For a detailed explanation of the LSL implementation, its event handlers, asynchronous requests, and polling behavior, see LSL_CLIENT_REFERENCE.md.

For the complete HTTP/Tide route reference, see TIDE_ROUTE_MANUAL.md.

Before using real objects or bridge jobs, run the copyable isolation and bridge checks in the Battle-Test Checklist section of the route manual.

Install and Configure

  1. Create or select a Second Life object that you own.
  2. Add Relote.Stack.Service.lsl to the object's inventory.
  3. Edit these values at the top of the script:

    string SERVERURL = "https://your-server.example";
    string BRIDGE_TOKEN = "";
    integer CHANNEL_INPUT = 1111;
    integer CHANNEL_OUTPUT = 0;
    
  4. Save or reset the script. The owner receives a startup message.

  5. Type commands in nearby chat, prefixed with the input channel. With the default configuration, every command begins with /1111.

Only the object's owner can use this script. Commands from other avatars are ignored.

First Successful Request

Set the output channel to public chat, then run a Forth expression:

/1111 channel 0
/1111 forth 2 3 + .

The first command selects where ordinary results are sent. The second sends Forth source to POST /forth/eval; . removes the top value from the Forth stack and includes it in the response output. You should see 5 in public chat.

Use a nonzero channel when results should not be public:

/1111 channel 42
/1111 forth 6 7 * .
/1111 forth-say 42 10 20 + .

Chat Command Reference

All commands below assume the default input channel 1111.

Command Purpose Example
channel <number> Set the normal output channel. /1111 channel 0
help Send owner-only syntax help. /1111 help
session <name> Select a named language session. /1111 session workshop_1
session-reset Return to this prim's UUID session. /1111 session-reset
forth <source> Run a one-line Forth program. /1111 forth 9 4 - .
forth-say <channel> <source> Run Forth on a chosen channel. /1111 forth-say 42 2 8 * .
ruby <source> Run one RubyForth statement or expression. /1111 ruby puts 2 + 3
ruby-say <channel> <source> Run RubyForth on a chosen channel. /1111 ruby-say 42 puts "hello"
algebra <expression> Analyze an integer polynomial. /1111 algebra 3*x^2 - 2*x + 7
algebra-say <channel> <expression> Send algebra output to a chosen channel. /1111 algebra-say 42 x^2 + 2*x + 1
set <name> <value> Store a server variable. /1111 set greeting hello
get <name> Read a server variable. /1111 get greeting
view List stored variables. /1111 view
delete <name> Delete one stored variable. /1111 delete greeting
clear Clear the server variable store. /1111 clear
status Request variable-store status. /1111 status
history Request variable-store history. /1111 history
file-write <name> <text> Save text in the sandboxed file store. /1111 file-write note.txt hello
file-read <name> Read a sandboxed file. /1111 file-read note.txt
file-read-say <channel> <name> Read a file on a chosen channel. /1111 file-read-say 42 note.txt
files List sandboxed files. /1111 files
file-delete <name> Delete a sandboxed file. /1111 file-delete note.txt
notecard <inventory-name> Upload an inventory notecard. /1111 notecard Counter
notecards List uploaded notecards. /1111 notecards
notecard-get <name> Fetch an uploaded notecard. /1111 notecard-get Counter
notecard-delete <name> Delete an uploaded notecard. /1111 notecard-delete Counter
forth-run <name> Run an uploaded Forth notecard. /1111 forth-run Counter
forth-run-say <channel> <name> Run a Forth notecard on a chosen channel. /1111 forth-run-say 42 Counter
ruby-run <name> Compile and run RubyForth. /1111 ruby-run RubyCounter
ruby-run-say <channel> <name> Run RubyForth on a chosen channel. /1111 ruby-run-say 42 RubyCounter
matrix-get <name> Read a persisted matrix. /1111 matrix-get A
matrix-get-say <channel> <name> Read a matrix on a chosen channel. /1111 matrix-get-say 42 A
bridge-on Start polling for browser-queued bridge jobs. /1111 bridge-on
bridge-off Stop bridge polling. /1111 bridge-off
bridge-poll Trigger one bridge poll immediately. /1111 bridge-poll

The command parser splits on spaces. Use chat commands for short, single-line source and use notecards for multiline Forth or RubyForth.

Server Variables

The set, get, view, delete, clear, status, and history commands use the server variable store. Values are server-persistent; the kv list in the LSL script is only a local response cache.

/1111 set score 125
/1111 get score
/1111 view
/1111 delete score

Avoid backslashes and these characters in names or values: :, [, ], {, }, single quotes, double quotes, and periods. The current LSL escape/unescape implementation does not round-trip those characters correctly. Plain names such as score, player_name, and simple text values are safe.

Forth Basics

Forth is stack-based. Numbers and strings are pushed onto the stack. Most words pop their inputs from the stack and push a result.

2 3 + .

This pushes 2, pushes 3, adds them, then prints 5.

Group Words
Arithmetic +, -, *, /, mod, abs, min, max
Comparisons =, !=, <, <=, >, >=, 0=
Boolean true, false, nil, not, and, or
Stack dup, drop, swap, over
Output ., puts, p
Time/randomness now, rand
Control if ... else ... then, begin ... until, begin ... again, bye
Variables let, variable, @, !, $name, name=, name+=, name-=
Named words : name ... ;

Numbers are signed integers. / is integer division. Strings use double quotes; a backslash begins a Forth comment outside a string.

Variables

Variables persist in the same server store used by Forth, RubyForth, matrices, and notecards.

let total
25 total !
$total 5 + total!
$total puts

total pushes a variable address for ! and @. $total pushes its current value. The concise forms total!, total+=, and total-= assign, add, and subtract respectively.

Named Forth Words

You can give a sequence of Forth tokens a name and use that name later in the same program or notecard. This is the main new tool for breaking a larger program into reusable pieces.

: square dup * ;
: announce-square square puts ;

6 announce-square

Read the first definition as: β€œdefine the word square; when it is used, copy the top stack value with dup, multiply the copy and original with *, then finish the definition at ;.” The second definition calls the first, then prints its result. The final line pushes 6 and calls announce-square, which prints 36.

Definitions are collected before execution, so a word may call another word defined later in the same source. They are expanded before the interpreter runs, which keeps existing loops and safety limits unchanged. Recursive definitions, such as : forever forever ;, are rejected. Do not reuse the name of a built-in word such as if, puts, or matrix.

Semicolon-Separated Forth

Outside a : name ... ; definition, a semicolon is a statement separator. This is useful in one-line chat commands:

/1111 forth 2 3 + puts; : square dup * ; 4 square puts;

The output is 5 16. The semicolon after square still closes the definition; the other semicolons simply separate sequential blocks.

For a short definition in chat:

/1111 forth : square dup * ; 9 square puts

Use a notecard for multi-line definitions.

Explicit Execution Budgets

The server defaults to 10,000 interpreter steps and accepts at most 1,000,000. Long loops now have direct LSL commands that set this budget explicitly:

/1111 forth-steps 100000 : square dup * ; 250 square puts
/1111 ruby-steps 100000 puts 2 + 3
/1111 forth-run-steps 100000 Counter
/1111 ruby-run-steps 100000 RubyCounter

The second argument to each *-steps command must be an integer from 1 to 1000000. A larger budget permits more computation, but does not remove the server's resource limit.

Forth and RubyForth in One Program

The ruby Forth directive compiles its quoted RubyForth source into the surrounding Forth program. The compiled code uses the same variables, named words, stack, output response, and allowed Second Life calls as the containing Forth source.

: square dup * ;
6 square puts
ruby "count = 4\nputs count * 3"

This prints 36 from Forth, then 12 from RubyForth. The \n is a newline inside the quoted RubyForth source. In a notecard, a multiline Ruby snippet is also allowed inside the quoted string.

RubyForth can send a quoted Forth source string back through the same expander:

forth ": double dup + ; 21 double puts"
puts "RubyForth continues after Forth"

The first line defines and calls a Forth word, printing 42; the second line is ordinary RubyForth and prints its text afterward. A forth bridge statement must contain exactly one quoted Forth source string.

For nearby chat, use the new Forth-originated alias:

/1111 hybrid : square dup * ; 6 square puts ruby "puts 2 + 3"
/1111 hybrid-say 42 ruby "puts 6 * 7"

hybrid uses /forth/eval, so it can start with Forth and include one or more ruby "..." directives. Regular /1111 forth accepts the same bridge syntax; hybrid only makes that intent explicit in chat.

Forth Notecard Examples

Create a notecard with the exact inventory name shown, paste its contents, place it in the same object as the script, and upload it with:

/1111 notecard <NotecardName>

Wait for the object to report that it is loading the notecard, then run it with /1111 forth-run <NotecardName>.

What a notecard is and what happens to it

A notecard is an inventory item inside the Second Life object. It is not a file on your computer, and it is not automatically a program. The script reads it one line at a time, sends its text to the server, and stores a server-side copy under the same name.

Use this exact sequence for every example:

  1. Create a new notecard in the object's inventory and name it exactly as the example heading says. For example, name the first one Counter.
  2. Open the notecard and paste only the code inside its matching code block. Do not paste the Markdown backticks, the heading, or explanatory prose.
  3. Save the notecard and close it.
  4. In nearby chat, as the object owner, enter /1111 notecard Counter.
  5. The LSL script asks Second Life for each line. When it reaches the end, it uploads the complete text to the server as the remote program named Counter.
  6. Wait for the loading message to appear. Large notecards can take longer than one chat message because Second Life returns their lines asynchronously.
  7. Run the uploaded copy with /1111 forth-run Counter.
  8. Read the result on the configured output channel. Set that channel first with /1111 channel 0 if you want public-chat output.

The inventory name and the command argument are case-sensitive in practice: use the same spelling every time. Editing the local notecard does not update the server copy. Run /1111 notecard Counter again after every edit, then rerun /1111 forth-run Counter.

forth-run executes source as Forth. ruby-run executes the same uploaded source as RubyForth. Do not run a Forth example with ruby-run, and do not run a RubyForth example with forth-run.

Counter

let counter
3 counter !

begin
    $counter puts
    1 counter-=
    $counter 0=
until

Expected output: 3, 2, 1.

Condition

let temperature
23 temperature !

$temperature 20 > if
    "warm" puts
else
    "cool" puts
then

StackTools

10 20 over . . .

over copies the second stack item. This example outputs 10, 20, then 10.

RandomNumber

100 rand puts

Outputs an integer from 0 through 99.

Greeting

"Hello, Second Life" puts
"Hello, " "RustForth" + puts

MatrixDeterminant

2 2 matrix A
1 0 0 mset A
2 0 1 mset A
3 1 0 mset A
4 1 1 mset A

m.show A
m.det A puts

This creates a two-by-two matrix and prints its serialized form followed by determinant -2.

MatrixScale

2 m.identity I
5 m.scale I FiveI
m.show FiveI

MatrixSolve

2 2 matrix A
2 0 0 mset A
0 0 1 mset A
2 1 0 mset A
1 1 1 mset A

2 1 matrix B
4 0 0 mset B
6 1 0 mset B

m.solve A B X
m.show X

This solves $A X = B$ when the solution contains exact integers.

Matrix Reference

Matrices persist server-side. The maximum dimension is 64 and the maximum number of cells is 4096.

Word Stack/source form Result
matrix <rows> <cols> matrix <name> Create a zero matrix.
m.identity <size> m.identity <name> Create an identity matrix.
mset <value> <row> <col> mset <name> Set one cell.
mget <row> <col> mget <name> Push one cell.
m.fill <value> m.fill <name> Fill every cell.
m.scale <scalar> m.scale <source> <target> Scale a matrix.
m.add m.add <left> <right> <target> Add matrices.
m.sub m.sub <left> <right> <target> Subtract matrices.
m.mul m.mul <left> <right> <target> Multiply matrices.
m.solve m.solve <left> <right> <target> Solve $A X = B$ with exact integers.
m.transpose m.transpose <source> <target> Transpose a matrix.
m.det m.det <name> Push determinant.
m.rows m.rows <name> Push row count.
m.cols m.cols <name> Push column count.
m.show m.show <name> Emit serialized matrix output.

Use /1111 matrix-get <name> to retrieve a stored matrix outside a Forth program.

Forth-Controlled Second Life Actions

The Forth runtime does not directly run arbitrary LSL. Instead, it returns an allowlisted calls list, and this script performs each call in the object. These actions work with forth, forth-run, and Forth bridge jobs. At most 128 host calls are allowed in one Forth program.

The action is performed when this LSL client receives the evaluation response, so a direct command such as /1111 forth ... can cause the action immediately.

Speak in chat

0 "Hello from the object" sl.say
0 "A quiet hello" sl.whisper
0 "Attention" sl.shout
42 "Private channel message" sl.region_say
"Only the owner sees this" sl.owner_say

The stack order for the first four forms is <channel> <message>.

Change floating text, color, and alpha

"Ready" 0 255 0 255 sl.set_text
255 0 0 -1 sl.set_color
128 -1 sl.set_alpha

Color and alpha components accept either 0 through 1 or 0 through 255. -1 means all faces for sl.set_color and sl.set_alpha.

Play a sound

Replace the UUID with a sound asset UUID in the object's inventory or a sound that the object is permitted to play:

"01234567-89ab-cdef-0123-456789abcdef" 1 sl.play_sound

The stack order is <sound> <volume>.

Object timer, position, and linked messages

5 sl.set_timer
128 128 25 sl.set_region_pos

sl.set_timer invokes llSetTimerEvent; it does not add custom timer handling to this script. The existing timer event is used for bridge polling, so do not use sl.set_timer while bridge polling is enabled unless changing the poll interval is intentional.

sl.set_region_pos requires the usual Second Life permissions and parcel conditions. sl.link_message has stack order <link> <code> <message> <id>; -1 is LINK_SET.

ObjectWelcome

"RustForth online" 0 200 100 255 sl.set_text
0 "Object is ready" sl.say
"Object is ready" sl.owner_say

PulseColor

0 128 255 -1 sl.set_color
255 -1 sl.set_alpha
0 "Color changed" sl.whisper

RubyForth

RubyForth is a small Ruby-shaped language compiled into the Forth runtime. It shares persistent variables, arrays, hashes, matrices, notecards, and execution budgets with Forth, but it is not MRI Ruby. It has no gems, require, eval, classes, arbitrary host methods, arbitrary file access, or system commands.

Supported constructs are integer and string expressions, #{name} interpolation, assignment, +=, -=, *=, /=, puts, p, if, unless, elsif, else, while, until, n.times do, end, return, one-line arrays/hashes, and collection helpers such as get, set, push, delete, keys, length, and has.

Semicolons separate RubyForth statements outside strings, arrays, hashes, and parentheses. This makes multi-step direct chat commands possible:

/1111 ruby count = 1; puts count; count += 1; puts count

Use a notecard for multiline blocks, not merely because of semicolons.

/1111 ruby puts 2 + 3
/1111 ruby puts "Hello from RubyForth"
/1111 ruby-say 42 puts 6 * 7

RubyForth currently cannot call sl.say, sl.set_text, or any other sl.* operation. Use a Forth notecard when the program must change the in-world object.

Mixing RubyForth and Forth

Use a quoted one-line bridge when the Forth fragment is short:

count = 2
forth "$count 3 + count="
puts count

For a larger Forth fragment, use a multiline forth do ... end block:

count = 2; puts count

forth do
    $count 3 + count=
    $count puts
end

puts count

This emits 2 5 5: RubyForth initializes and prints count, Forth adds three and prints it, and RubyForth reads the same persisted value afterward. Forth inside this block can use named words, matrices, arrays, hashes, or allowed sl.* calls. RubyForth resumes after the block using the same execution budget.

Forth can call a short RubyForth fragment in the other direction:

ruby "count = 4; puts count * 3"

Per-Prim Sessions

At startup, the LSL client sets its language session to the current prim UUID. That means Forth/RubyForth state from two objects using the same server can be isolated. Change the active scope only when you intentionally want to share state:

/1111 session workshop_1
/1111 ruby score = 10; puts score
/1111 session-reset

Session names must use letters, numbers, _, or -, and be at most 64 characters. Existing API callers that omit session_id keep using the legacy default session.

RubyForth Notecard Examples

Upload each notecard with /1111 notecard <NotecardName>, then run it with /1111 ruby-run <NotecardName>.

RubyCounter

count = 0
while count < 3
    puts count
    count += 1
end

Expected output: 0, 1, 2.

RubyTimes

5.times do
    puts "tick"
end

RubyCondition

score = 80
if score >= 90
    puts "A"
elsif score >= 80
    puts "B"
else
    puts "Needs work"
end

RubyUnless

enabled = false
unless enabled
    puts "Feature is disabled"
end

RubyArithmetic

total = 4
total *= 3
total += 2
puts total

Expected output: 14.

RubyStrings

first = "Second"
second = "Life"
puts first + " " + second

RubyUntil

remaining = 3
until remaining == 0
    puts remaining
    remaining -= 1
end

Files and Notecard Management

The server has two separate persistence mechanisms:

  • Notecards store source programs in the partitioned server store. Upload inventory notecards with notecard; run them with forth-run or ruby-run.
  • Files are UTF-8 text documents in a sandboxed file store. Manage them with file-write, file-read, files, and file-delete. They are not executable unless their contents are submitted as program source.
/1111 file-write ideas.txt build a counter notecard
/1111 file-read ideas.txt
/1111 files
/1111 file-delete ideas.txt

/1111 notecard Counter
/1111 notecards
/1111 forth-run Counter
/1111 notecard-delete Counter

Browser-to-Second-Life Bridge

The bridge lets the browser console queue Forth or RubyForth source. A Second Life object with this script polls the queue and evaluates one queued job at a time.

Server Setup

Set a nonempty secret before starting the server:

export MSSL_FORTH_BRIDGE_TOKEN='replace-with-a-long-random-token'

Set that exact same value in the script:

string BRIDGE_TOKEN = "replace-with-a-long-random-token";

Do not publish the token in a public script. Anyone who knows it can enqueue jobs for a bridge-enabled object.

Object Setup

After saving or resetting the configured script:

/1111 bridge-on

The object polls every three seconds. Stop polling with:

/1111 bridge-off

bridge-poll sends one immediate poll request, which is useful while diagnosing connectivity.

Queue a Job

Open https://your-server.example/forth/ui. Paste Forth source, enter the bridge token, and choose Queue Forth for SL. For example:

"Queued job reached Second Life" sl.owner_say

Use Queue Ruby for SL only for RubyForth computation or output. RubyForth does not currently produce sl.* calls, so it will not modify the in-world object.

Troubleshooting

Symptom Check
No startup message Confirm the object contains a saved script and inspect the Second Life script error window.
No command response Use /1111, confirm you own the object, and verify SERVERURL is reachable over HTTPS.
Output appears in the wrong place Send /1111 channel 0, or use a *-say command with an explicit channel.
Forth reports stack underflow Supply the word's inputs. For sl.say, push channel and then message.
Ruby parsing fails Use one statement per line, omit semicolons, and use only the documented RubyForth subset.
Notecard upload fails Ensure the notecard is in the same object's inventory and its name exactly matches the command.
Bridge refuses to start Set a nonempty BRIDGE_TOKEN in the script.
Bridge receives no job Ensure the server and script use the same MSSL_FORTH_BRIDGE_TOKEN, then use /1111 bridge-poll.
Variable text is malformed Avoid special characters listed in the variable warning until escaping is corrected.
Object action is ignored Confirm it came from Forth rather than RubyForth, and check normal Second Life permissions and parcel restrictions.

Practical Workflow

  1. Use /1111 forth ... for quick arithmetic or a short test.
  2. Move multiline logic into a named inventory notecard.
  3. Upload with /1111 notecard Name after every notecard edit.
  4. Use forth-run for object actions and ruby-run for compact control-flow programs and server-side calculations.
  5. Enable the bridge only when browser-queued work must reach a specific in-world object.

RubyForth Guide

Second Life RustForth Service Guide

Relote.Stack.Service.lsl is an owner-controlled Second Life client for the Tide/RustForth server. It accepts chat commands, sends HTTPS requests to the server, and can perform allowlisted server-generated actions in the object that contains the script.

This guide covers chat commands, Forth, RubyForth, persisted notecards, matrices, files, and the optional browser-to-Second-Life bridge.

For a detailed explanation of the LSL implementation, its event handlers, asynchronous requests, and polling behavior, see LSL_CLIENT_REFERENCE.md.

For the complete HTTP/Tide route reference, see TIDE_ROUTE_MANUAL.md.

Before using real objects or bridge jobs, run the copyable isolation and bridge checks in the Battle-Test Checklist section of the route manual.

Install and Configure

  1. Create or select a Second Life object that you own.
  2. Add Relote.Stack.Service.lsl to the object's inventory.
  3. Edit these values at the top of the script:

    string SERVERURL = "https://your-server.example";
    string BRIDGE_TOKEN = "";
    integer CHANNEL_INPUT = 1111;
    integer CHANNEL_OUTPUT = 0;
    
  4. Save or reset the script. The owner receives a startup message.

  5. Type commands in nearby chat, prefixed with the input channel. With the default configuration, every command begins with /1111.

Only the object's owner can use this script. Commands from other avatars are ignored.

First Successful Request

Set the output channel to public chat, then run a Forth expression:

/1111 channel 0
/1111 forth 2 3 + .

The first command selects where ordinary results are sent. The second sends Forth source to POST /forth/eval; . removes the top value from the Forth stack and includes it in the response output. You should see 5 in public chat.

Use a nonzero channel when results should not be public:

/1111 channel 42
/1111 forth 6 7 * .
/1111 forth-say 42 10 20 + .

Chat Command Reference

All commands below assume the default input channel 1111.

Command Purpose Example
channel <number> Set the normal output channel. /1111 channel 0
help Send owner-only syntax help. /1111 help
session <name> Select a named language session. /1111 session workshop_1
session-reset Return to this prim's UUID session. /1111 session-reset
forth <source> Run a one-line Forth program. /1111 forth 9 4 - .
forth-say <channel> <source> Run Forth on a chosen channel. /1111 forth-say 42 2 8 * .
ruby <source> Run one RubyForth statement or expression. /1111 ruby puts 2 + 3
ruby-say <channel> <source> Run RubyForth on a chosen channel. /1111 ruby-say 42 puts "hello"
algebra <expression> Analyze an integer polynomial. /1111 algebra 3*x^2 - 2*x + 7
algebra-say <channel> <expression> Send algebra output to a chosen channel. /1111 algebra-say 42 x^2 + 2*x + 1
set <name> <value> Store a server variable. /1111 set greeting hello
get <name> Read a server variable. /1111 get greeting
view List stored variables. /1111 view
delete <name> Delete one stored variable. /1111 delete greeting
clear Clear the server variable store. /1111 clear
status Request variable-store status. /1111 status
history Request variable-store history. /1111 history
file-write <name> <text> Save text in the sandboxed file store. /1111 file-write note.txt hello
file-read <name> Read a sandboxed file. /1111 file-read note.txt
file-read-say <channel> <name> Read a file on a chosen channel. /1111 file-read-say 42 note.txt
files List sandboxed files. /1111 files
file-delete <name> Delete a sandboxed file. /1111 file-delete note.txt
notecard <inventory-name> Upload an inventory notecard. /1111 notecard Counter
notecards List uploaded notecards. /1111 notecards
notecard-get <name> Fetch an uploaded notecard. /1111 notecard-get Counter
notecard-delete <name> Delete an uploaded notecard. /1111 notecard-delete Counter
forth-run <name> Run an uploaded Forth notecard. /1111 forth-run Counter
forth-run-say <channel> <name> Run a Forth notecard on a chosen channel. /1111 forth-run-say 42 Counter
ruby-run <name> Compile and run RubyForth. /1111 ruby-run RubyCounter
ruby-run-say <channel> <name> Run RubyForth on a chosen channel. /1111 ruby-run-say 42 RubyCounter
matrix-get <name> Read a persisted matrix. /1111 matrix-get A
matrix-get-say <channel> <name> Read a matrix on a chosen channel. /1111 matrix-get-say 42 A
bridge-on Start polling for browser-queued bridge jobs. /1111 bridge-on
bridge-off Stop bridge polling. /1111 bridge-off
bridge-poll Trigger one bridge poll immediately. /1111 bridge-poll

The command parser splits on spaces. Use chat commands for short, single-line source and use notecards for multiline Forth or RubyForth.

Server Variables

The set, get, view, delete, clear, status, and history commands use the server variable store. Values are server-persistent; the kv list in the LSL script is only a local response cache.

/1111 set score 125
/1111 get score
/1111 view
/1111 delete score

Avoid backslashes and these characters in names or values: :, [, ], {, }, single quotes, double quotes, and periods. The current LSL escape/unescape implementation does not round-trip those characters correctly. Plain names such as score, player_name, and simple text values are safe.

Forth Basics

Forth is stack-based. Numbers and strings are pushed onto the stack. Most words pop their inputs from the stack and push a result.

2 3 + .

This pushes 2, pushes 3, adds them, then prints 5.

Group Words
Arithmetic +, -, *, /, mod, abs, min, max
Comparisons =, !=, <, <=, >, >=, 0=
Boolean true, false, nil, not, and, or
Stack dup, drop, swap, over
Output ., puts, p
Time/randomness now, rand
Control if ... else ... then, begin ... until, begin ... again, bye
Variables let, variable, @, !, $name, name=, name+=, name-=
Named words : name ... ;

Numbers are signed integers. / is integer division. Strings use double quotes; a backslash begins a Forth comment outside a string.

Variables

Variables persist in the same server store used by Forth, RubyForth, matrices, and notecards.

let total
25 total !
$total 5 + total!
$total puts

total pushes a variable address for ! and @. $total pushes its current value. The concise forms total!, total+=, and total-= assign, add, and subtract respectively.

Named Forth Words

You can give a sequence of Forth tokens a name and use that name later in the same program or notecard. This is the main new tool for breaking a larger program into reusable pieces.

: square dup * ;
: announce-square square puts ;

6 announce-square

Read the first definition as: β€œdefine the word square; when it is used, copy the top stack value with dup, multiply the copy and original with *, then finish the definition at ;.” The second definition calls the first, then prints its result. The final line pushes 6 and calls announce-square, which prints 36.

Definitions are collected before execution, so a word may call another word defined later in the same source. They are expanded before the interpreter runs, which keeps existing loops and safety limits unchanged. Recursive definitions, such as : forever forever ;, are rejected. Do not reuse the name of a built-in word such as if, puts, or matrix.

Semicolon-Separated Forth

Outside a : name ... ; definition, a semicolon is a statement separator. This is useful in one-line chat commands:

/1111 forth 2 3 + puts; : square dup * ; 4 square puts;

The output is 5 16. The semicolon after square still closes the definition; the other semicolons simply separate sequential blocks.

For a short definition in chat:

/1111 forth : square dup * ; 9 square puts

Use a notecard for multi-line definitions.

Explicit Execution Budgets

The server defaults to 10,000 interpreter steps and accepts at most 1,000,000. Long loops now have direct LSL commands that set this budget explicitly:

/1111 forth-steps 100000 : square dup * ; 250 square puts
/1111 ruby-steps 100000 puts 2 + 3
/1111 forth-run-steps 100000 Counter
/1111 ruby-run-steps 100000 RubyCounter

The second argument to each *-steps command must be an integer from 1 to 1000000. A larger budget permits more computation, but does not remove the server's resource limit.

Forth and RubyForth in One Program

The ruby Forth directive compiles its quoted RubyForth source into the surrounding Forth program. The compiled code uses the same variables, named words, stack, output response, and allowed Second Life calls as the containing Forth source.

: square dup * ;
6 square puts
ruby "count = 4\nputs count * 3"

This prints 36 from Forth, then 12 from RubyForth. The \n is a newline inside the quoted RubyForth source. In a notecard, a multiline Ruby snippet is also allowed inside the quoted string.

RubyForth can send a quoted Forth source string back through the same expander:

forth ": double dup + ; 21 double puts"
puts "RubyForth continues after Forth"

The first line defines and calls a Forth word, printing 42; the second line is ordinary RubyForth and prints its text afterward. A forth bridge statement must contain exactly one quoted Forth source string.

For nearby chat, use the new Forth-originated alias:

/1111 hybrid : square dup * ; 6 square puts ruby "puts 2 + 3"
/1111 hybrid-say 42 ruby "puts 6 * 7"

hybrid uses /forth/eval, so it can start with Forth and include one or more ruby "..." directives. Regular /1111 forth accepts the same bridge syntax; hybrid only makes that intent explicit in chat.

Forth Notecard Examples

Create a notecard with the exact inventory name shown, paste its contents, place it in the same object as the script, and upload it with:

/1111 notecard <NotecardName>

Wait for the object to report that it is loading the notecard, then run it with /1111 forth-run <NotecardName>.

What a notecard is and what happens to it

A notecard is an inventory item inside the Second Life object. It is not a file on your computer, and it is not automatically a program. The script reads it one line at a time, sends its text to the server, and stores a server-side copy under the same name.

Use this exact sequence for every example:

  1. Create a new notecard in the object's inventory and name it exactly as the example heading says. For example, name the first one Counter.
  2. Open the notecard and paste only the code inside its matching code block. Do not paste the Markdown backticks, the heading, or explanatory prose.
  3. Save the notecard and close it.
  4. In nearby chat, as the object owner, enter /1111 notecard Counter.
  5. The LSL script asks Second Life for each line. When it reaches the end, it uploads the complete text to the server as the remote program named Counter.
  6. Wait for the loading message to appear. Large notecards can take longer than one chat message because Second Life returns their lines asynchronously.
  7. Run the uploaded copy with /1111 forth-run Counter.
  8. Read the result on the configured output channel. Set that channel first with /1111 channel 0 if you want public-chat output.

The inventory name and the command argument are case-sensitive in practice: use the same spelling every time. Editing the local notecard does not update the server copy. Run /1111 notecard Counter again after every edit, then rerun /1111 forth-run Counter.

forth-run executes source as Forth. ruby-run executes the same uploaded source as RubyForth. Do not run a Forth example with ruby-run, and do not run a RubyForth example with forth-run.

Counter

let counter
3 counter !

begin
    $counter puts
    1 counter-=
    $counter 0=
until

Expected output: 3, 2, 1.

Condition

let temperature
23 temperature !

$temperature 20 > if
    "warm" puts
else
    "cool" puts
then

StackTools

10 20 over . . .

over copies the second stack item. This example outputs 10, 20, then 10.

RandomNumber

100 rand puts

Outputs an integer from 0 through 99.

Greeting

"Hello, Second Life" puts
"Hello, " "RustForth" + puts

MatrixDeterminant

2 2 matrix A
1 0 0 mset A
2 0 1 mset A
3 1 0 mset A
4 1 1 mset A

m.show A
m.det A puts

This creates a two-by-two matrix and prints its serialized form followed by determinant -2.

MatrixScale

2 m.identity I
5 m.scale I FiveI
m.show FiveI

MatrixSolve

2 2 matrix A
2 0 0 mset A
0 0 1 mset A
2 1 0 mset A
1 1 1 mset A

2 1 matrix B
4 0 0 mset B
6 1 0 mset B

m.solve A B X
m.show X

This solves $A X = B$ when the solution contains exact integers.

Matrix Reference

Matrices persist server-side. The maximum dimension is 64 and the maximum number of cells is 4096.

Word Stack/source form Result
matrix <rows> <cols> matrix <name> Create a zero matrix.
m.identity <size> m.identity <name> Create an identity matrix.
mset <value> <row> <col> mset <name> Set one cell.
mget <row> <col> mget <name> Push one cell.
m.fill <value> m.fill <name> Fill every cell.
m.scale <scalar> m.scale <source> <target> Scale a matrix.
m.add m.add <left> <right> <target> Add matrices.
m.sub m.sub <left> <right> <target> Subtract matrices.
m.mul m.mul <left> <right> <target> Multiply matrices.
m.solve m.solve <left> <right> <target> Solve $A X = B$ with exact integers.
m.transpose m.transpose <source> <target> Transpose a matrix.
m.det m.det <name> Push determinant.
m.rows m.rows <name> Push row count.
m.cols m.cols <name> Push column count.
m.show m.show <name> Emit serialized matrix output.

Use /1111 matrix-get <name> to retrieve a stored matrix outside a Forth program.

Forth-Controlled Second Life Actions

The Forth runtime does not directly run arbitrary LSL. Instead, it returns an allowlisted calls list, and this script performs each call in the object. These actions work with forth, forth-run, and Forth bridge jobs. At most 128 host calls are allowed in one Forth program.

The action is performed when this LSL client receives the evaluation response, so a direct command such as /1111 forth ... can cause the action immediately.

Speak in chat

0 "Hello from the object" sl.say
0 "A quiet hello" sl.whisper
0 "Attention" sl.shout
42 "Private channel message" sl.region_say
"Only the owner sees this" sl.owner_say

The stack order for the first four forms is <channel> <message>.

Change floating text, color, and alpha

"Ready" 0 255 0 255 sl.set_text
255 0 0 -1 sl.set_color
128 -1 sl.set_alpha

Color and alpha components accept either 0 through 1 or 0 through 255. -1 means all faces for sl.set_color and sl.set_alpha.

Play a sound

Replace the UUID with a sound asset UUID in the object's inventory or a sound that the object is permitted to play:

"01234567-89ab-cdef-0123-456789abcdef" 1 sl.play_sound

The stack order is <sound> <volume>.

Object timer, position, and linked messages

5 sl.set_timer
128 128 25 sl.set_region_pos

sl.set_timer invokes llSetTimerEvent; it does not add custom timer handling to this script. The existing timer event is used for bridge polling, so do not use sl.set_timer while bridge polling is enabled unless changing the poll interval is intentional.

sl.set_region_pos requires the usual Second Life permissions and parcel conditions. sl.link_message has stack order <link> <code> <message> <id>; -1 is LINK_SET.

ObjectWelcome

"RustForth online" 0 200 100 255 sl.set_text
0 "Object is ready" sl.say
"Object is ready" sl.owner_say

PulseColor

0 128 255 -1 sl.set_color
255 -1 sl.set_alpha
0 "Color changed" sl.whisper

RubyForth

RubyForth is a small Ruby-shaped language compiled into the Forth runtime. It shares persistent variables, arrays, hashes, matrices, notecards, and execution budgets with Forth, but it is not MRI Ruby. It has no gems, require, eval, classes, arbitrary host methods, arbitrary file access, or system commands.

Supported constructs are integer and string expressions, #{name} interpolation, assignment, +=, -=, *=, /=, puts, p, if, unless, elsif, else, while, until, n.times do, end, return, one-line arrays/hashes, and collection helpers such as get, set, push, delete, keys, length, and has.

Semicolons separate RubyForth statements outside strings, arrays, hashes, and parentheses. This makes multi-step direct chat commands possible:

/1111 ruby count = 1; puts count; count += 1; puts count

Use a notecard for multiline blocks, not merely because of semicolons.

/1111 ruby puts 2 + 3
/1111 ruby puts "Hello from RubyForth"
/1111 ruby-say 42 puts 6 * 7

RubyForth currently cannot call sl.say, sl.set_text, or any other sl.* operation. Use a Forth notecard when the program must change the in-world object.

Mixing RubyForth and Forth

Use a quoted one-line bridge when the Forth fragment is short:

count = 2
forth "$count 3 + count="
puts count

For a larger Forth fragment, use a multiline forth do ... end block:

count = 2; puts count

forth do
    $count 3 + count=
    $count puts
end

puts count

This emits 2 5 5: RubyForth initializes and prints count, Forth adds three and prints it, and RubyForth reads the same persisted value afterward. Forth inside this block can use named words, matrices, arrays, hashes, or allowed sl.* calls. RubyForth resumes after the block using the same execution budget.

Forth can call a short RubyForth fragment in the other direction:

ruby "count = 4; puts count * 3"

Per-Prim Sessions

At startup, the LSL client sets its language session to the current prim UUID. That means Forth/RubyForth state from two objects using the same server can be isolated. Change the active scope only when you intentionally want to share state:

/1111 session workshop_1
/1111 ruby score = 10; puts score
/1111 session-reset

Session names must use letters, numbers, _, or -, and be at most 64 characters. Existing API callers that omit session_id keep using the legacy default session.

RubyForth Notecard Examples

Upload each notecard with /1111 notecard <NotecardName>, then run it with /1111 ruby-run <NotecardName>.

RubyCounter

count = 0
while count < 3
    puts count
    count += 1
end

Expected output: 0, 1, 2.

RubyTimes

5.times do
    puts "tick"
end

RubyCondition

score = 80
if score >= 90
    puts "A"
elsif score >= 80
    puts "B"
else
    puts "Needs work"
end

RubyUnless

enabled = false
unless enabled
    puts "Feature is disabled"
end

RubyArithmetic

total = 4
total *= 3
total += 2
puts total

Expected output: 14.

RubyStrings

first = "Second"
second = "Life"
puts first + " " + second

RubyUntil

remaining = 3
until remaining == 0
    puts remaining
    remaining -= 1
end

Files and Notecard Management

The server has two separate persistence mechanisms:

  • Notecards store source programs in the partitioned server store. Upload inventory notecards with notecard; run them with forth-run or ruby-run.
  • Files are UTF-8 text documents in a sandboxed file store. Manage them with file-write, file-read, files, and file-delete. They are not executable unless their contents are submitted as program source.
/1111 file-write ideas.txt build a counter notecard
/1111 file-read ideas.txt
/1111 files
/1111 file-delete ideas.txt

/1111 notecard Counter
/1111 notecards
/1111 forth-run Counter
/1111 notecard-delete Counter

Browser-to-Second-Life Bridge

The bridge lets the browser console queue Forth or RubyForth source. A Second Life object with this script polls the queue and evaluates one queued job at a time.

Server Setup

Set a nonempty secret before starting the server:

export MSSL_FORTH_BRIDGE_TOKEN='replace-with-a-long-random-token'

Set that exact same value in the script:

string BRIDGE_TOKEN = "replace-with-a-long-random-token";

Do not publish the token in a public script. Anyone who knows it can enqueue jobs for a bridge-enabled object.

Object Setup

After saving or resetting the configured script:

/1111 bridge-on

The object polls every three seconds. Stop polling with:

/1111 bridge-off

bridge-poll sends one immediate poll request, which is useful while diagnosing connectivity.

Queue a Job

Open https://your-server.example/forth/ui. Paste Forth source, enter the bridge token, and choose Queue Forth for SL. For example:

"Queued job reached Second Life" sl.owner_say

Use Queue Ruby for SL only for RubyForth computation or output. RubyForth does not currently produce sl.* calls, so it will not modify the in-world object.

Troubleshooting

Symptom Check
No startup message Confirm the object contains a saved script and inspect the Second Life script error window.
No command response Use /1111, confirm you own the object, and verify SERVERURL is reachable over HTTPS.
Output appears in the wrong place Send /1111 channel 0, or use a *-say command with an explicit channel.
Forth reports stack underflow Supply the word's inputs. For sl.say, push channel and then message.
Ruby parsing fails Use one statement per line, omit semicolons, and use only the documented RubyForth subset.
Notecard upload fails Ensure the notecard is in the same object's inventory and its name exactly matches the command.
Bridge refuses to start Set a nonempty BRIDGE_TOKEN in the script.
Bridge receives no job Ensure the server and script use the same MSSL_FORTH_BRIDGE_TOKEN, then use /1111 bridge-poll.
Variable text is malformed Avoid special characters listed in the variable warning until escaping is corrected.
Object action is ignored Confirm it came from Forth rather than RubyForth, and check normal Second Life permissions and parcel restrictions.

Practical Workflow

  1. Use /1111 forth ... for quick arithmetic or a short test.
  2. Move multiline logic into a named inventory notecard.
  3. Upload with /1111 notecard Name after every notecard edit.
  4. Use forth-run for object actions and ruby-run for compact control-flow programs and server-side calculations.
  5. Enable the bridge only when browser-queued work must reach a specific in-world object.




Back to infinivaeria's Main Blog Page

πŸ“ 0.00164s [1.63555ms]


♾️ [355656] -- (c) 2026-2032 MiaedScore.online -- (v#️⃣30.0.0f-wasm):[ πŸ—οΈSeptember 2 2026 - 10:00AM ]

October, 11, 2026 - 06:32:02 PM SLT/PST




🏘️[🌐216.73.216.5]

[βž•πŸ”’]|[βž–πŸ”’]





    # Average length of a full lunar cycle (in days)
MOON_CYCLE_DAYS = 29.53

# The 27 fabled moon rotations with emojis:
MOON_ROTATIONS = [
  'New Moon πŸŒ‘', # 1
  'Waxing Crescent πŸŒ’',     # 2
  'First Quarter πŸŒ“',       # 3
  'Waxing Gibbous πŸŒ”',      # 4
  'Full Moon πŸŒ•',           # 5
  'Waning Gibbous πŸŒ–',      # 6
  'Last Quarter πŸŒ—',        # 7
  'Waning Crescent 🌘',     # 8
  'Supermoon 🌝',           # 9
  'Blue Moon πŸ”΅πŸŒ™',         # 10
  'Blood Moon πŸ©ΈπŸŒ™',        # 11
  'Harvest Moon πŸ‚πŸŒ•',      # 12
  "Hunter's Moon πŸŒ™πŸ”­",     # 13
  'Wolf Moon πŸΊπŸŒ•',         # 14
  'Pink Moon πŸŒΈπŸŒ•', # 15
  'Snow Moon 🌨️', # 16
  'Snow Moon Snow πŸŒ¨οΈβ„οΈ', # 17
  'Avian Moon πŸ¦…', # 18
  'Avian Moon Snow πŸ¦…β„οΈ',    # 19
  'Skunk Moon 🦨',           # 20
  'Skunk Moon Snow πŸ¦¨β„οΈ',    # 21
  'Cosmic Moon πŸŒŒπŸŒ•', # 22
  'Celestial Moon πŸŒŸπŸŒ•', # 23
  'Otter Moon πŸ•πŸŒŒ', # 24
  'Muskium Otter Muskium Stinky Stimky Otter Moon 🦨🌌', # 25
  'Light Elemental Moon πŸ’‘πŸŒ•', # 26
  'Dark Elemental Moon πŸŒ‘πŸŒ•' # 27

]
# Define 27 corresponding species with emojis.
SPECIES = [
  'Dogg 🐢', # New Moon
  'Folf 🦊🐺', # Waxing Crescent
  'Aardwolf 🐾',
  'Spotted Hyena πŸ†',
  'Folf Hybrid 🦊✨',
  'Striped Hyena πŸ¦“',
  'Dogg Prime πŸ•β­',
  'WolfFox 🐺🦊', # Waning Crescent
  'Brown Hyena 🦴',
  'Dogg Celestial πŸ•πŸŒŸ',
  'Folf Eclipse πŸ¦ŠπŸŒ’',
  'Aardwolf Luminous 🐾✨',
  'Spotted Hyena Stellar πŸ†β­',
  'Folf Nova 🦊πŸ’₯',
  'Brown Hyena Cosmic 🦴🌌',
  'Snow Leopard 🌨️', # New Moon
  'Snow Leopard Snow Snep πŸŒ¨οΈβ„οΈ',
  'Avian πŸ¦…',
  'Avian Snow πŸ¦…β„οΈ',
  'Skunk 🦨',
  'Skunk Snow πŸ¦¨β„οΈ',
  'Infini-Vaeria Graevity-Infini πŸŒŒπŸ•',
  'Graevity-Infini Infini-Vaeria πŸŒŸπŸ•',
  'Otter 🦦',
  'Muskium Otter Stinky Stimky 🦦🦨',
  'Light Elf πŸ’‘',
  'Light Elf Cosmic πŸŒ‘'

]

# Define 27 corresponding were-forms with emojis.
WERE_FORMS = [
  'WereDogg πŸΆπŸŒ‘',
  'WereFolf πŸ¦ŠπŸŒ™',
  'WereAardwolf 🐾',
  'WereSpottedHyena πŸ†',
  'WereFolfHybrid 🦊✨',
  'WereStripedHyena πŸ¦“',
  'WereDoggPrime πŸ•β­',
  'WereWolfFox 🐺🦊', # Waning Crescent
  'WereBrownHyena 🦴',
  'WereDoggCelestial πŸ•πŸŒŸ',
  'WereFolfEclipse πŸ¦ŠπŸŒ’',
  'WereAardwolfLuminous 🐾✨',
  'WereSpottedHyenaStellar πŸ†β­',
  'WereFolfNova 🦊πŸ’₯', # Wolf Moon
  'WereBrownHyenaCosmic 🦴🌌', # Pink Moon
  'WereSnowLeopard πŸ†β„οΈ',
  'WereSnowLeopardSnow πŸ†β„οΈβ„οΈ', # Pink Moon
  'WereAvian πŸ¦…', # New Moon
  'WereAvianSnow πŸ¦…β„οΈ', # Pink Moon
  'WereSkunk 🦨', # New Moon
  'WereSkunkSnow πŸ¦¨β„οΈ', # New Moon
  'WereInfiniVaeriaGraevity πŸ•πŸŒŒ',
  'WereGraevityInfiniInfiniVaeria πŸŒŸπŸ•',
  'WereOtter 🦦',
  'WereMuskiumOtterStinkyStimky 🦦🦨',
  'WereLightElf πŸ’‘',
  'WereLightElfCosmic πŸŒ‘'
]


# Normal Moon Phases
  PHASES = [
    ["New Moon", "πŸŒ‘"],
    ["Waxing Crescent I", "πŸŒ’"],
    ["Waxing Crescent II", "πŸŒ’"],
    ["First Quarter", "πŸŒ“"],
    ["Waxing Gibbous I", "πŸŒ”"],
    ["Waxing Gibbous II", "πŸŒ”"],
    ["Full Moon", "πŸŒ•"],
    ["Waning Gibbous I", "πŸŒ–"],
    ["Waning Gibbous II", "πŸŒ–"],
    ["Last Quarter", "πŸŒ—"],
    ["Waning Crescent I", "🌘"],
    ["Waning Crescent II", "🌘"],
    ["Dark Moon", "πŸŒ‘"]
  ]