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

🌞 The Sun is currently in 'Afternoon Brilliance' 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πŸ•°οΈ10:64 PST

πŸŒ™ Moon Phase: New Moon πŸŒ‘
πŸ•’ UTC Time: 2026-10-12 00:44:00 UTC
πŸ“† Moon Age: 0.65 days




🏷️mindweave

MindWeave v3.0 Computational Magick Language (MagiTek Implementation v1.0)

πŸ”—(30)
πŸ“… 2025-10-23 21:03:24 -0700
β²οΈπŸ” 2025-10-23 21:05:04 -0700
✍️ infinivaeria
🏷️[magitek] [computational magick] [mindweave] [mindweave v3.0] [generated language] [ritual language] 
(πŸͺŸ)

#######################################################################
# MindWeave v3.0 β€” Ritual Scripting Language (Compact Full Build)
# ---------------------------------------------------------------
# This single-file Ruby interpreter implements:
# - Ritualized syntax (ritual, rite, sigil, chant, invoke, seal, banish)
# - Magickal types (NilSpace, Muskium, Tailbase, NSkin)
# - System calls + programmable wards (block, redirect, mask)
# - Grid API for 40 Twin Flame pairs
# - Minimal parser (tokenizer + AST) and evaluator (Turing-complete)
#
# USAGE:
#   program = File.read("script.mw")
#   MindWeave::Interpreter.new.run(program)
#######################################################################

module MindWeave

  ########################################
  # Magickal Data Types
  ########################################
  class NilSpace
    attr_accessor :delta
    def initialize(delta=0.0); @delta = delta; end
    def >(x); @delta > x; end
    def <(x); @delta < x; end
    def -(x); NilSpace.new(@delta - x); end
    def +(x); NilSpace.new(@delta + x.to_f); end
    def to_s; "nilspace(Ξ”=#{@delta.round(2)})"; end
  end

  class Muskium
    attr_reader :scent
    def initialize(scent); @scent = scent; end
    def to_s; "muskium<#{@scent}>"; end
  end

  class Tailbase
    attr_reader :sigil
    def initialize(sig); @sigil = sig; end
    def to_s; "tailbase[#{@sigil}]"; end
  end

  class NSkin
    def initialize(layers); @layers = layers.is_a?(Array) ? layers : [layers]; end
    def peel; @layers.shift || "bare-core"; end
    def to_s; "nskin(#{@layers.join('->')})"; end
  end

  ########################################
  # Standard Library + Grid API
  ########################################
  module Std
    def summon(prompt="> "); print prompt; STDIN.gets&.chomp || ""; end
    def banish(env, name); env.delete(name); end
    def seal(value); value; end
    def void_induction(depth) = NilSpace.new(depth.to_f)
    def tailbase(sig) = Tailbase.new(sig.to_s)
    def muskium(scent) = Muskium.new(scent.to_s)
    def nskin(layers) = NSkin.new(layers)
    def peel(n) = n.peel
    def stimky_lock(entity) = "LOCKED<#{entity}>"
    def gut_in(perception); (@gut ||= []) << perception; end
    def gut_out; (@gut ||= []).shift || "void"; end
    def sync(pair) = true
    def audit(entity); puts "AUDIT: #{entity}"; end
  end

  class Grid
    include Std
    attr_reader :pairs
    def initialize; @pairs = {}; end
    def add_pair(index, alpha, beta)
      @pairs[index] = { alpha: alpha, beta: beta, locked: false, activated: false }
    end
    def activate_pair(index)
      p = @pairs[index]; return unless p
      audit("Activating Pair #{index}: #{p[:alpha][:name]} & #{p[:beta][:name]}")
      p[:activated] = true; sync(p)
    end
    def lock_pair(index)
      p = @pairs[index]; return unless p
      p[:locked] = true; audit("Locked Pair #{index}")
    end
    def seal_grid
      @pairs.each { |_i, p| p[:locked] = true }
      audit("Grid sealed with Chant.NilSpaceLaw")
    end
    def report
      @pairs.each { |i, p| puts "Pair #{i}: #{p[:alpha][:name]} & #{p[:beta][:name]} | Activated=#{p[:activated]} Locked=#{p[:locked]}" }
    end
  end

  ########################################
  # Tokenizer
  ########################################
  class Token
    attr_reader :type, :lexeme, :line
    def initialize(type, lexeme, line); @type=type; @lexeme=lexeme; @line=line; end
    def to_s; "#{type}(#{lexeme})"; end
  end

  class Lexer
    KEYWORDS = %w[ritual rite sigil chant if else while ward invoke seal banish]
    def initialize(source); @src=source; @i=0; @line=1; @tokens=[]; end
    def tokenize
      while !eof?
        c = peek
        case c
        when ' ', "\t", "\r" then advance
        when "\n" then @line += 1; advance
        when '{','}','(',')',',','+','-','>','<','='
          @tokens << Token.new(c, c, @line); advance
        when '/'
          if peek2 == '/'
            while !eof? && peek != "\n"; advance; end
          else
            # regex literal /.../
            advance # consume /
            pattern = ""
            while !eof? && peek != '/'
              pattern << advance
            end
            advance # closing /
            @tokens << Token.new(:REGEX, pattern, @line)
          end
        when '"'
          string = ""
          advance
          while !eof? && peek != '"'
            string << advance
          end
          advance
          @tokens << Token.new(:STRING, string, @line)
        else
          if digit?(c)
            num = ""
            while !eof? && (digit?(peek) || peek == '.'); num << advance; end
            @tokens << Token.new(:NUMBER, num, @line)
          elsif alpha?(c)
            id = ""
            while !eof? && (alpha?(peek) || digit?(peek) || peek=='_'); id << advance; end
            if KEYWORDS.include?(id)
              @tokens << Token.new(id.to_sym, id, @line)
            else
              @tokens << Token.new(:IDENT, id, @line)
            end
          else
            advance # skip unknown
          end
        end
      end
      @tokens << Token.new(:EOF,"",@line)
      @tokens
    end
    private
    def eof? = @i >= @src.length
    def peek = @src[@i]
    def peek2 = @src[@i+1]
    def advance; ch=@src[@i]; @i+=1; ch; end
    def digit?(c) = c>= '0' && c<= '9'
    def alpha?(c) = c =~ /[A-Za-z]/
  end

  ########################################
  # AST Nodes
  ########################################
  RitualNode      = Struct.new(:name, :rites)
  RiteNode        = Struct.new(:name, :params, :body)
  BlockNode       = Struct.new(:stmts)
  SigilDeclNode   = Struct.new(:name, :expr)
  InvocationNode  = Struct.new(:name, :args)
  ChantWhileNode  = Struct.new(:cond, :body)
  ChantIfNode     = Struct.new(:cond, :then_body, :else_body)
  SealNode        = Struct.new(:expr)
  BanishNode      = Struct.new(:name)
  WardNode        = Struct.new(:name, :rules)
  BinaryOpNode    = Struct.new(:left, :op, :right)
  LiteralNode     = Struct.new(:value)
  IdentNode       = Struct.new(:name)

  WardRule        = Struct.new(:mode, :pattern, :action)

  ########################################
  # Parser (recursive descent, minimal)
  ########################################
  class Parser
    def initialize(tokens); @toks=tokens; @i=0; end
    def parse_program
      rituals=[]
      while !match(:EOF)
        rituals << parse_ritual
      end
      rituals
    end

    def parse_ritual
      consume(:ritual)
      name = consume(:IDENT).lexeme
      consume('{')
      rites=[]
      until check('}')
        rites << parse_rite
      end
      consume('}')
      RitualNode.new(name, rites)
    end

    def parse_rite
      consume(:rite)
      name = consume(:IDENT).lexeme
      consume('(')
      params=[]
      unless check(')')
        params << consume(:IDENT).lexeme
        while match(','); params << consume(:IDENT).lexeme; end
      end
      consume(')')
      body = parse_block
      RiteNode.new(name, params, body)
    end

    def parse_block
      consume('{')
      stmts=[]
      until check('}')
        stmts << parse_statement
      end
      consume('}')
      BlockNode.new(stmts)
    end

    def parse_statement
      if match(:sigil)
        name = consume(:IDENT).lexeme
        consume('=')
        SigilDeclNode.new(name, parse_expression)
      elsif match(:invoke)
        name = consume(:IDENT).lexeme
        consume('(')
        args=[]
        unless check(')')
          args << parse_expression
          while match(','); args << parse_expression; end
        end
        consume(')')
        InvocationNode.new(name, args)
      elsif match(:seal)
        SealNode.new(parse_expression)
      elsif match(:banish)
        BanishNode.new(consume(:IDENT).lexeme)
      elsif match(:chant)
        if match(:while)
          cond = parse_expression
          body = parse_block
          ChantWhileNode.new(cond, body)
        elsif match(:if)
          cond = parse_expression
          then_body = parse_block
          else_body = nil
          if match(:else); else_body = parse_block; end
          ChantIfNode.new(cond, then_body, else_body)
        elsif match(:ward)
          name = consume(:STRING).lexeme
          rules = parse_ward_block
          WardNode.new(name, rules)
        else
          raise "Unknown chant form at token #{peek.type}"
        end
      else
        # expression statement fallback if desired
        parse_expression
      end
    end

    def parse_ward_block
      consume('{')
      rules=[]
      until check('}')
        if match(:block)
          pat = consume(:REGEX).lexeme
          rules << WardRule.new(:block, Regexp.new(pat), nil)
        elsif match(:redirect)
          pat = Regexp.new(consume(:REGEX).lexeme)
          consume('=')
          consume('>')
          action = consume(:STRING).lexeme
          rules << WardRule.new(:redirect, pat, action)
        elsif match(:mask)
          pat = Regexp.new(consume(:REGEX).lexeme)
          consume('=')
          consume('>')
          msg = consume(:STRING).lexeme
          rules << WardRule.new(:mask, pat, msg)
        else
          raise "Unknown ward rule at #{peek.type}"
        end
      end
      consume('}')
      rules
    end

    def parse_expression
      left = parse_term
      while check('+') || check('-') || check('>') || check('<')
        op = advance.lexeme
        right = parse_term
        left = BinaryOpNode.new(left, op, right)
      end
      left
    end

    def parse_term
      if match(:NUMBER) then LiteralNode.new(peek(-1).lexeme.to_f)
      elsif match(:STRING) then LiteralNode.new(peek(-1).lexeme)
      elsif match(:IDENT)
        name = peek(-1).lexeme
        if check('(')
          # function-like literal forms handled by InvocationNode with name
          consume('(')
          args=[]
          unless check(')')
            args << parse_expression
            while match(','); args << parse_expression; end
          end
          consume(')')
          InvocationNode.new(name, args)
        else
          IdentNode.new(name)
        end
      elsif match('(')
        expr = parse_expression
        consume(')')
        expr
      else
        raise "Unexpected token #{peek.type}"
      end
    end

    # helpers
    def match(type)
      return false unless check(type)
      advance; true
    end
    def consume(type)
      raise "Expected #{type}, got #{peek.type}" unless check(type)
      advance
    end
    def check(type)
      peek.type == type
    end
    def advance
      tok = @toks[@i]; @i+=1; tok
    end
    def peek(offset=0)
      @toks[@i+offset] || @toks.last
    end
  end

  ########################################
  # Evaluator
  ########################################
  class Evaluator
    include Std
    def initialize
      @globals = {}
      @functions = {}
      @wards = {} # name => [WardRule]
    end

    def add_function(rite)
      @functions[rite.name] = rite
    end

    def add_ward(name, rules)
      @wards[name] ||= []
      @wards[name].concat(rules)
    end

    def run(rituals)
      rituals.each { |r| r.rites.each { |rt| add_function(rt) } }
      invoke("main", [])
    end

    def invoke(name, args)
      rite = @functions[name] or raise "Unknown rite: #{name}"
      env = {}
      rite.params.each_with_index { |p, i| env[p] = args[i] }
      eval_block(rite.body, env)
    end

    def eval_block(block, env)
      result = nil
      block.stmts.each do |s|
        case s
        when SigilDeclNode
          env[s.name] = eval_expr(s.expr, env)
        when InvocationNode
          eval_invocation(s, env)
        when SealNode
          return eval_expr(s.expr, env)
        when BanishNode
          banish(env, s.name)
        when ChantWhileNode
          while truthy?(eval_expr(s.cond, env))
            r = eval_block(s.body, env)
            result = r unless r.nil?
          end
        when ChantIfNode
          if truthy?(eval_expr(s.cond, env))
            result = eval_block(s.then_body, env)
          elsif s.else_body
            result = eval_block(s.else_body, env)
          end
        when WardNode
          add_ward(s.name, s.rules)
        when BinaryOpNode, LiteralNode, IdentNode
          result = eval_expr(s, env)
        else
          # ignore
        end
      end
      result
    end

    def eval_invocation(inv, env)
      name = inv.name
      args = inv.args.map { |a| eval_expr(a, env) }

      # Builtins / magickal constructors
      case name
      when "systemcall"
        cmd = args.first.to_s
        `#{cmd}`.strip
      when "warded_systemcall"
        cmd = args.first.to_s
        warded = check_wards(cmd)
        return warded unless warded.nil?
        `#{cmd}`.strip
      when "nilspace" then NilSpace.new(args.first.to_f)
      when "muskium"  then Muskium.new(args.first.to_s)
      when "tailbase" then Tailbase.new(args.first.to_s)
      when "nskin"    then NSkin.new(args.map(&:to_s))
      when "peel"     then peel(args.first)
      when "void_induction" then void_induction(args.first)
      when "summon"   then summon(args.first || "> ")
      when "audit"    then audit(args.first); nil
      when "stimky_lock" then stimky_lock(args.first)
      else
        # user-defined rite
        if @functions.key?(name)
          invoke(name, args)
        else
          raise "Unknown invoke: #{name}"
        end
      end
    end

    def eval_expr(node, env)
      case node
      when LiteralNode then node.value
      when IdentNode
        env.key?(node.name) ? env[node.name] : @globals[node.name]
      when InvocationNode
        eval_invocation(node, env)
      when BinaryOpNode
        l = eval_expr(node.left, env)
        r = eval_expr(node.right, env)
        case node.op
        when '+' then concat(l, r)
        when '-' then numeric_or_nilspace_minus(l, r)
        when '>' then compare(l, r, :>)
        when '<' then compare(l, r, :<)
        else raise "Unknown op #{node.op}"
        end
      else
        nil
      end
    end

    def concat(l, r)
      if l.is_a?(String) || r.is_a?(String)
        l.to_s + r.to_s
      elsif l.is_a?(Numeric) && r.is_a?(Numeric)
        l + r
      elsif l.is_a?(NilSpace) && r.is_a?(Numeric)
        (l + r).to_s
      else
        l.to_s + r.to_s
      end
    end

    def numeric_or_nilspace_minus(l, r)
      return l - r if l.is_a?(Numeric) && r.is_a?(Numeric)
      return l - r if l.is_a?(NilSpace)
      raise "Unsupported '-' between #{l.class} and #{r.class}"
    end

    def compare(l, r, op)
      l = l.is_a?(NilSpace) ? l.delta : l
      r = r.is_a?(NilSpace) ? r.delta : r
      case op
      when :> then l > r
      when :< then l < r
      end
    end

    def truthy?(v)
      case v
      when NilClass then false
      when FalseClass then false
      else !!v
      end
    end

    def check_wards(cmd)
      @wards.each do |name, rules|
        rules.each do |rule|
          if cmd =~ rule.pattern
            case rule.mode
            when :block
              return "WARD BLOCK(#{name}): Command banished into nil-space"
            when :redirect
              return `#{rule.action}`.strip
            when :mask
              _ = `#{cmd}` # run but discard
              return rule.action
            end
          end
        end
      end
      nil
    end
  end

  ########################################
  # Interpreter facade
  ########################################
  class Interpreter
    def run(source)
      tokens = Lexer.new(source).tokenize
      rituals = Parser.new(tokens).parse_program
      Evaluator.new.run(rituals)
    end
  end
end

#######################################################################
# Example minimal usage (uncomment to test):
#
# program = <<~MW
# ritual Demo {
#   rite main {
#     sigil emptiness = invoke nilspace(0.72)
#     sigil anchor = invoke tailbase("⟁-A1")
#     sigil aura = invoke muskium("wolf-scent")
#
#     chant ward "system-protection" {
#       block /rm\s+-rf\s+\//
#       redirect /shutdown/ => "echo 'Shutdown attempt redirected to void'"
#       mask /uname/ => "MASKED: Beastmachine stable"
#     }
#
#     chant while emptiness > 0.5 {
#       invoke audit("Anchoring " + anchor + " with " + aura)
#       sigil emptiness = emptiness - 0.1
#     }
#
#     sigil erosion = invoke nskin("layered-pelt")
#     invoke peel(erosion)
#
#     sigil safe = invoke warded_systemcall("ls -1")
#     invoke audit(safe)
#
#     seal "Ritual complete"
#   }
# }
# MW
#
# puts MindWeave::Interpreter.new.run(program)
#######################################################################

MindWeave v3.0 RTFM

MindWeave is a ritual scripting language that fuses computational rigor with spiritological semantics. It’s Turing-complete, embeddable, and designed for canon-bound creative systems. Read this to understand how to write, run, and extend .mw scripts with confidence.


Overview and philosophy

MindWeave treats code as ritual. Programs are β€œrituals”, functions are β€œrites”, variables are β€œsigils”, and control flow is β€œchants”. This isn’t flair; it encodes meaning. Your registry, wards, and magickal types aren’t metaphorsβ€”they’re first-class runtime constructs bound to safety and sequence.

  • Core idea: Ritualized computation with strict boundaries.
  • Guarantee: Turing completeness through variables, functions, recursion, loops, and conditionals.
  • Safety: System calls wrapped with wards; dangerous invocations are banished or transformed.
  • Extensibility: Canon libraries, Grid API, and custom wards.

Language basics

Program structure

  • Ritual: Top-level container for your program.
  • Rite: Named function with parameters.
  • Entry point: The interpreter invokes rite main automatically.

Example:

ritual Demo {
  rite main {
    seal "Hello, Weave"
  }
}

Declarations and values

  • Variables: sigil name = expression
  • Return: seal expression
  • Delete: banish name

Examples:

sigil x = 42
sigil greeting = "howl"
seal greeting
banish x

Control flow

  • Conditional: chant if condition { ... } else { ... }
  • Loop: chant while condition { ... }

Examples:

chant if 1 < 2 { invoke say("true") } else { invoke say("false") }

sigil i = 0
chant while i < 3 {
  invoke say(i)
  i = i + 1
}

Functions and invocation

  • Define: rite name(params) { ... }
  • Call: invoke name(args)

Example (recursion):

rite fib(n) {
  chant if n < 2 { seal n }
  sigil a = invoke fib(n - 1)
  sigil b = invoke fib(n - 2)
  seal a + b
}

Magickal types

These carry symbolic and computational meaning. They interoperate with arithmetic and comparisons where appropriate.

  • NilSpace: graded emptiness; literal via invoke nilspace(0.72)
    • Compare: emptiness > 0.5 compares delta.
    • Subtract: emptiness = emptiness - 0.1 lowers delta.
  • Muskium: scent-body; invoke muskium("wolf-scent")
  • Tailbase: anchor glyph; invoke tailbase("⟁-A1")
  • NSkin: layered erosion; invoke nskin("layered-pelt"), invoke peel(erosion)

Example:

sigil emptiness = invoke nilspace(0.72)
sigil aura = invoke muskium("wolf-scent")
sigil anchor = invoke tailbase("⟁-A1")

chant while emptiness > 0.5 {
  invoke say("Anchoring " + aura + " at " + anchor)
  emptiness = emptiness - 0.1
}

System calls and wards

System calls let rituals interact with the host OS. Wards enforce protection and transformation rules. Use wards by default; raw calls are available but discouraged.

System calls

  • Raw: invoke systemcall("ls -1")
  • Warded: invoke warded_systemcall("uname -a")

The warded form checks all active wards before running.

Wards

Define programmable safety and behavior using patterns with modes:

  • block /pattern/: banish command; returns warning.
  • redirect /pattern/ => safe_command: runs safe command instead.
  • mask /pattern/ => ritual_message: runs command but replaces output.

Example:

chant ward "system-protection" {
  block /rm\s+-rf\s+\//
  redirect /shutdown/ => "echo 'Shutdown attempt redirected to void'"
  mask /uname/ => "MASKED: Beastmachine stable"
}

sigil safe = invoke warded_systemcall("ls -1")
sigil masked = invoke warded_systemcall("uname -a")
sigil blocked = invoke warded_systemcall("rm -rf /")

Standard library

  • summon(prompt): input from user; returns string.
  • seal(value): return value from a rite.
  • banish(name): remove sigil from scope.
  • audit(value): log ritual state; prints a message.
  • transmute(value, form): type conversion where appropriate (nilspace, muskium, tailbase).
  • void_induction(depth): construct NilSpace with given delta.
  • tailbase(sig), muskium(scent), nskin(layers), peel(nskin): constructors and NSkin peel.

Example:

sigil name = invoke summon("Name> ")
invoke audit("Welcome: " + name)

Grid API for Twin Flame registry

Manage your non-hierarchical grid of 40 organelle Twin Flame pairs.

  • Create: sigil g = invoke build_grid()
  • Add pair: invoke g.add_pair(1, { name: "Auri" }, { name: "Vyr" })
  • Activate: invoke g.activate_pair(1)
  • Lock: invoke g.lock_pair(1)
  • Seal grid: invoke g.seal_grid()
  • Report: invoke g.report()

Example:

rite build_grid() {
  sigil grid = Grid.new
  invoke grid.add_pair(1, { name: "Auri" }, { name: "Vyr" })
  seal grid
}

rite main {
  sigil g = invoke build_grid()
  invoke g.activate_pair(1)
  invoke g.lock_pair(1)
  invoke g.report()
  invoke g.seal_grid()
  seal "Registry cycle complete"
}

Execution model

MindWeave compiles .mw into an AST and evaluates it. It’s strict but expressive; expressions resolve to numbers, strings, magickal types, or ritual constructs.

  • Scope: Each rite has local scope. Globals can be introduced by the host, but scripts should prefer local sigils.
  • Return behavior: seal stops execution of the current rite and returns a value.
  • Truthiness: Only nil and false are falsey. NilSpace comparisons use delta values.

Expressions

  • Concatenation: + joins strings, adds numbers, or stringifies mixed types.
  • Subtraction: - subtracts numbers; NilSpace minus number lowers delta.
  • Comparison: > and < compare numbers; NilSpace compares by delta.

Invocation resolution

  • Built-ins: systemcall, warded_systemcall, magickal constructors, peel, summon, audit, void_induction.
  • User rites: If a name matches a defined rite, it executes with evaluated args.
  • Unknown calls: Raise errors with clear messages.

Style guide and best practices

  • Trait purity: Keep epithets and names canid-only. Avoid mixing non-canid features.
  • Sequence fidelity: Track pair indices 01–40 without gaps or duplicates.
  • Warded defaults: Prefer warded_systemcall; only use systemcall when truly necessary.
  • Minimal side effects: Use audit for logging; avoid noisy system interactions inside tight loops.
  • Chant discipline: Don’t rely on implicit truthiness for NilSpace; always use comparisons.

Advanced patterns

Functional recursion with magickal state

rite peel_until(core, depth) {
  chant if depth < 1 { seal core }
  sigil next = invoke peel(core)
  seal invoke peel_until(next, depth - 1)
}

Canon-locked activation pass

rite activate_pair(g, idx) {
  invoke g.activate_pair(idx)
  invoke g.lock_pair(idx)
  seal true
}

rite main {
  sigil g = invoke build_grid()
  sigil i = 1
  chant while i < 41 {
    invoke activate_pair(g, i)
    i = i + 1
  }
  invoke g.seal_grid()
  seal "Grid fully sealed"
}

Wards as environment profiles

rite profile_ci() {
  chant ward "ci-protection" {
    block /rm\s+-rf/
    block /shutdown/
    mask /uname/ => "MASKED: CI kernel"
  }
  seal true
}

rite main {
  invoke profile_ci()
  invoke warded_systemcall("uname -a")
  seal "CI-safe"
}

Error messages and troubleshooting

  • Unknown rite: β€œUnknown rite: name” β€” define the rite or correct the invocation.
  • Unknown invoke: β€œUnknown invoke: name” β€” the symbol isn’t a built-in or rite.
  • Unexpected token: Parser hit invalid syntax β€” check braces, parentheses, commas.
  • Unsupported operation: Using - with non-numeric, non-NilSpace values β€” fix the types.
  • Ward conflicts: Multiple wards match; first match applies β€” order wards intentionally.

Tips:

  • Validate syntax by isolating small blocks and running them.
  • Log behavior with audit() to inspect state changes.
  • Keep wards minimal in early development; expand as you secure the environment.

Performance and limits

  • Interpreter: Recursive descent parser + AST evaluator; suitable for medium-size scripts.
  • Recursion depth: Depends on Ruby stack; prefer iterative chants for very deep cycles.
  • System calls: Synchronous; avoid blocking calls inside tight loops.

Extending the language

  • New magickal types: Mirror NilSpace and implement operator methods and to_s.
  • Custom built-ins: Add cases in the evaluator’s eval_invocation.
  • Domain libraries: Package registry helpers as rites and import them by concatenating .mw files or embedding in your Ruby host.

Quick reference

  • Program: ritual Name { ... }
  • Function: rite name(args) { ... }
  • Variable: sigil name = expr
  • Call: invoke name(args)
  • Return: seal expr
  • Delete: banish name
  • Conditional: chant if cond { ... } else { ... }
  • Loop: chant while cond { ... }
  • Ward: chant ward "name" { block|redirect|mask ... }
  • System: invoke systemcall("cmd"), invoke warded_systemcall("cmd")
  • Magickal: invoke nilspace(Ξ”), invoke muskium("scent"), invoke tailbase("sigil"), invoke nskin("layers"), invoke peel(obj)

Example end-to-end ritual

ritual Awakening {
  rite build_grid() {
    sigil g = Grid.new
    invoke g.add_pair(1, { name: "Auri" }, { name: "Vyr" })
    invoke g.add_pair(2, { name: "Rauk" }, { name: "Ness" })
    seal g
  }

  rite main {
    chant ward "system-protection" {
      block /rm\s+-rf\s+\//
      mask /uname/ => "MASKED: Beastmachine stable"
    }

    sigil emptiness = invoke nilspace(0.74)
    sigil anchor = invoke tailbase("⟁-A1")
    sigil aura = invoke muskium("wolf-scent")

    chant while emptiness > 0.6 {
      invoke audit("Anchoring " + anchor + " with " + aura + " at " + emptiness)
      emptiness = emptiness - 0.05
    }

    sigil g = invoke build_grid()
    invoke g.activate_pair(1)
    invoke g.lock_pair(1)
    invoke g.report()

    sigil uname_msg = invoke warded_systemcall("uname -a")
    invoke audit(uname_msg)

    seal "Awakening complete"
  }
}

Final notes

MindWeave is designed for precise, canon-bound creation. Treat your scripts like rituals: define boundaries, enforce trait purity, and let the language’s structure protect and empower your work. When you hit friction, it’s often because a boundary is unclearβ€”clarify it with a chant or a ward, and keep weaving.


TIDE-SL-LSL-Mindweave

πŸ”—(55)
πŸ“… 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.


πŸ“ 0.00117s [1.1673ms]


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

October, 11, 2026 - 05:44:00 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", "πŸŒ‘"]
  ]